1. 程式人生 > >Python協程爬取妹子圖(內有福利,你懂得~)

Python協程爬取妹子圖(內有福利,你懂得~)

split 基本 保存文件 切換 代碼執行 怎麽辦 什麽 head .cn

項目說明:

  1、項目介紹

    本項目使用Python提供的協程+scrapy中的選擇器的使用(相當好用)實現爬取妹子圖的(福利圖)圖片,這個學會了,某榴什麽的、pow(2, 10)是吧!

  2、用到的知識點

    本項目中會用到以下知識點

    ① Python的編程(本人使用版本3.6.2)

    ② 使用scrapy中的css選擇器

    ③ 使用async協程

    ④ 使用aiohttp異步訪問url

    ⑤ 使用aiofiles異步保存文件

  3、 項目效果圖

技術分享

項目實現:

  我們最終的目的是把圖片的標題替換成需要保存的目錄,下面的圖片呢,就按著網頁上圖片的名稱保存~,有了這個需求以後,ok,社會我demon哥,人很話不多,開幹!

   我們需要網站的入口,入口如下~就爬取萌妹子吧!

   妹子圖中萌妹分類的網站入口:http://www.meizitu.com/a/cute.html  

   打開萌妹子的入口鏈接以後,我們需要分析下網頁中結構,然後通過分析頁面,獲取我們有用的內容:

   通過入口我們得知,url地址中,有兩個我們需要關系的點,一個是妹子圖的妹子類型,一個是要獲取頁面的頁碼,如果獲取多頁的話,也就是替換成不同的頁碼即可(圖如下)

   技術分享

   分析完上面的頁面以後,我們在來分析當前頁中需要提取的信息 ,使用Chrome瀏覽器打開開發者模式(windows是F12,MacOS是command+option+i)

   技術分享

   點擊剛剛選中妹子的url的地址,我們在來分析這裏面的有用信息

   技術分享

   信息提取就到這裏,我們下面需要使用css選擇器,提取url然後開始寫方法,來下載這些圖片

   沒有安裝的scrapy的趕緊去pip3 install scrapy一下,要麽您老就右上角的小叉叉退出吧~ 不然沒辦法進行了!

   Scrapy提供一個Shell的參數命令了,在這個參數後面加上你要提取頁面中的url地址,就可以進入到scrapy shell中,在裏面可以通過css xpath選擇器調試提取信息,用法如下:

   在終端輸入: scrapy shell http://www.meizitu.com/a/cute.html


    技術分享

   出現上面的即可,這裏面有個response,我們可以通過response.css或者reponses.xpath獲取url的數據,ok..我這裏使用css來提取,為嘛?! 簡單唄~

   css的具體語法嘛~~大家不會的話,可以自行百度,或者去菜鳥站補一補知識,我這人比較懶,我就不講了!直接告訴你們怎麽提取吧~可以通過Chrome給我提供的開發者工具來獲取css選擇器的表達式,請看下圖

   技術分享

   上面圖的圖很眼熟對吧,嗯,這是哪個主頁圖,我們需要在當前頁面中獲取所有妹子的url地址,然後在進入到每個妹子的url地址中獲取這個妹子的所有圖片!首先先來獲取當前頁面的所有妹子的url地址,切換到scrap shell中,通過response.css來提取信息

   提取妹子的url地址: response.css(‘#maincontent a::attr(href)‘).extract()

   技術分享

   嘿~,當前頁面的中的所有妹子的url都有了,那就好辦了呀,在進入這些地址中逐個獲取妹子獨立頁面中的url地址,然後下載就好咯!但是,大家有木有發現,這些頁面中的url有重復的,怎麽辦呢,用set可以去重哦,先來寫個獲取當前頁面的簡單的方法,一會我們在修改這個方法。

 1 import requests
 2 from scrapy import Selector
 3 
 4 
 5 def get_page_items(*, start_page_num: int=1, end_page_num: int=2, step: int=1):
 6     items = []
 7     for page_num in range(start_page_num, end_page_num, step):
 8         base_url = http://www.meizitu.com/a/{genre}_{page_num}.html
 9         req = requests.get(base_url.format(genre=cute, page_num=1))
10         content = req.content.decode(gbk)
11         selector = Selector(text=content)
12         item_urls = list(set(selector.css(#maincontent a::attr(href)).extract()))
13         items.extend(url for url in item_urls if url.startswith(http://www.meizitu.com/a/))
14     return items
15 
16 
17 print(get_page_items())

上面的代碼可以供我們拿下指定頁面中的所有漂亮小姐姐的url地址哦~,有了這些漂亮小姐姐的url,進入這個url以後,在提取小姐姐頁面的所有url就可以下載啦~!

 1 import requests
 2 from scrapy import Selector
 3 
 4 
 5 def get_page_items(*, start_page_num: int=1, end_page_num: int=2, step: int=1):
 6     items = []
 7     for page_num in range(start_page_num, end_page_num, step):
 8         base_url = http://www.meizitu.com/a/{genre}_{page_num}.html
 9         req = requests.get(base_url.format(genre=cute, page_num=1))
10         content = req.content.decode(gbk)
11         selector = Selector(text=content)
12         item_urls = list(set(selector.css(#maincontent a::attr(href)).extract()))
13         items.extend(url for url in item_urls if url.startswith(http://www.meizitu.com/a/))
14     return items
15 
16 
17 def get_images(item):
18     req = requests.get(item)
19     content = req.content.decode(gbk)
20     selector = Selector(text=content)
21     image_urls = list(set(selector.css(#maincontent p img::attr(src)).extract()))
22     print(image_urls)
23 
24 
25 for item in get_page_items():
26     get_images(item)

上面代碼執行的結果為:

技術分享

可以看到的效果,所有小姐姐的下載圖片的地址都已經拿到了,但是上面的代碼有兩個問題,聰明的小夥伴,可能已經發現了,上面代碼的重合性太高,那些獲取url的咚咚,都可以整合,在下面的一版,我們來改寫這個函數,有了這些圖片的地址,我們只需要調取某個函數或者方法,來下載這些圖片保存到本地即可,怎麽玩?! 往下看.....

 1 # _*_coding: utf-8_*_
 2 import os
 3 from time import perf_counter
 4 from functools import wraps
 5 
 6 import requests
 7 from scrapy import Selector
 8 """
 9 -------------------------------------------------
10    File Name:     妹子圖_串行
11    Description :
12    Author :        demon
13    date:          06/10/2017
14 -------------------------------------------------
15    Change Activity:
16                    06/10/2017:
17 -------------------------------------------------
18 """
19 __author__ = demon
20 
21 
22 def timer(func):
23     """
24     :param func: 裝飾器的函數,記錄方法所消耗的時間
25     :return:
26     """
27     @wraps(func)
28     def wrapper(*args, **kwargs):
29         start_time = perf_counter()
30         result = func(*args, **kwargs)
31         end_time = perf_counter()
32         cls_name = func.__name__
33         fmt = {cls_name} {args} spend time: {time:.5f}
34         print(fmt.format(cls_name=cls_name, args=args, time=end_time - start_time))
35         return result
36     return wrapper
37 
38 
39 def get_content_css(url):
40     req = requests.get(url)
41     content = req.content.decode(gbk)
42     selector = Selector(text=content)
43     return selector
44 
45 
46 def get_page_items(*, start_page_num: int=1, end_page_num: int=2, step: int=1):
47     items = []
48     for page_num in range(start_page_num, end_page_num, step):
49         base_url = http://www.meizitu.com/a/{genre}_{page_num}.html
50         selector = get_content_css(base_url.format(genre=cute, page_num=page_num))
51         item_urls = list(set(selector.css(#maincontent a::attr(href)).extract()))
52         items.extend(url for url in item_urls if url.startswith(http://www.meizitu.com/a/))
53     return items
54 
55 
56 def get_images(item):
57     selector = get_content_css(item)
58     image_urls = list(set(selector.css(#maincontent p img::attr(src)).extract()))
59     dir_name = selector.css(#maincontent div.metaRight h2 a::text).extract_first()
60     ok if os.path.exists(dir_name) else os.mkdir(dir_name)
61     for url in image_urls:
62         download_image(dir_name, url)
63 
64 
65 @timer
66 def download_image(dir_name, image_url):
67     headers = {User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_0) 
68                              AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36}
69     req = requests.get(image_url, headers=headers)
70     image = req.content
71     filename = image_url.rsplit(/, 1)[-1]
72     save_path = os.path.join(dir_name, filename)
73     with open(save_path, wb) as f:
74         f.write(image)
75 
76 
77 if __name__ == "__main__":
78     start = perf_counter()
79     for item in get_page_items():
80         get_images(item)
81     end = perf_counter()
82     print(format(end, *^100))
83     print(download all images cost time:{:.3f}.format(end - start))

上面的代碼可以保證圖片保存到本地,那麽基本的代碼邏輯沒有問題了,保存文件(download_image)也實現了~, 但是 但是這不是我們想要的效果,這玩意很慢的,一個一個並行下來的,要TMD天荒地老呀!

技術分享

臥槽,不能忍受呀,一個頁面就要用121秒的時間,這尼瑪的要是10頁20頁的不得瘋了呀!一定要改,改代碼,改成協程~,以下是三頁的數據才用時190秒呀,提升了不是一點半點呀!

技術分享

說幹就幹,改成協程,直接上全部代碼吧!因為...我懶得...寫了,這篇博客...寫了將近五個小時了...臥槽!要瘋了~

  1 # _*_coding: utf-8_*_
  2 import os
  3 import asyncio
  4 from functools import wraps
  5 from time import perf_counter
  6 
  7 import aiohttp
  8 import aiofiles
  9 from scrapy import Selector
 10 """
 11 -------------------------------------------------
 12    File Name:     妹子圖
 13    Description :
 14    Author :        demon
 15    date:          06/10/2017
 16 -------------------------------------------------
 17    Change Activity:
 18                    06/10/2017:
 19 -------------------------------------------------
 20 """
 21 __author__ = demon
 22 
 23 
 24 def timer(func):
 25     """
 26     :param func: 裝飾器的函數,記錄方法所消耗的時間
 27     :return:
 28     """
 29     @wraps(func)
 30     def wrapper(*args, **kwargs):
 31         start_time = perf_counter()
 32         result = func(*args, **kwargs)
 33         end_time = perf_counter()
 34         cls_name = func.__name__
 35         print({cls_name} spend time: {time:.5f}.format(cls_name=cls_name, time=end_time - start_time))
 36         return result
 37     return wrapper
 38 
 39 
 40 class MeiZiTuDownload:
 41     def __init__(self, *, genre: str=cute, start_page_num: int=1, end_page_num: int=5, step: int=1):
 42         self.base_url = http://www.meizitu.com/a/{genre}_{page_num}.html
 43         self.start_num = start_page_num
 44         self.end_num = end_page_num
 45         self.step = step
 46         self.genre = genre
 47         self.headers = {User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_0) 
 48                                       AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36}
 49 
 50     async def get_html_content(self, url: str):
 51         """
 52         :param url: 網頁的url地址
 53         :return:    網頁的html源碼
 54         """
 55         req = await aiohttp.request(GET, url, headers=self.headers)
 56         content = await req.read()
 57         content = content.decode(gbk)
 58         return content
 59 
 60     async def get_page_item(self, page_num: int):
 61         """
 62         :param page_num: 獲取網頁中的每一頁中的具體的url地址
 63         :return:
 64         """
 65         item_url = self.base_url.format(genre=self.genre, page_num=page_num)
 66         content = await self.get_html_content(item_url)
 67         selector = Selector(text=content)
 68         urls = list(set(selector.css(#maincontent a::attr(href)).extract()))
 69         page_items = (url for url in urls if url.startswith(http://www.meizitu.com/a/))
 70         for item in page_items:
 71             await self.get_item(item)
 72 
 73     async def get_item(self, item: str):
 74         """
 75         :param item: 單獨的下載頁面
 76         :return:
 77         """
 78         item_content = await self.get_html_content(item)
 79         selector = Selector(text=item_content)
 80         dir_name = selector.css(#maincontent div.metaRight h2 a::text).extract_first()
 81         image_urls = selector.css(#picture p img::attr(src)).extract()
 82         ok if os.path.exists(dir_name) else os.mkdir(dir_name)
 83         for image_url in image_urls:
 84             image_name = image_url.rsplit(/, 1)[-1]
 85             save_path = os.path.join(dir_name, image_name)
 86             await self.download_images(save_path, image_url)
 87 
 88     async def download_images(self, save_path: str, image_url: str):
 89         """
 90         :param save_path: 保存圖片的路徑
 91         :param image_url: 圖片的下載的url地址
 92         :return:
 93         """
 94         req = await aiohttp.request(GET, image_url, headers=self.headers)
 95         image = await req.read()
 96         fp = await aiofiles.open(save_path, wb)
 97         await fp.write(image)
 98 
 99     async def __call__(self, page_num: int):
100         await self.get_page_item(page_num)
101 
102     def __repr__(self):
103         cls_name = type(self).__name__
104         return {cls_name}{args}.format(cls_name=cls_name, args=(self.genre, self.start_num, self.end_num, self.step))
105 
106 
107 if __name__ == "__main__":
108     start = perf_counter()
109     download = MeiZiTuDownload(genre=cute)
110     loop = asyncio.get_event_loop()
111     to_do = [download(num) for num in range(1, 4)]
112     wait_future = asyncio.wait(to_do)
113     resp, _ = loop.run_until_complete(wait_future)
114     loop.close()
115     end = perf_counter()
116     func_name = download.__class__.__name__
117     spend_time = end - start
118     print(format(end, *^100))
119     print({func_name} spend time: {time:.5f}.format(func_name=func_name, time=spend_time))

協程的使用,大家移步到廖大神的哪裏學習下吧~~~,我就不講了...不然我要瘋了...我要看會電影,緩一會。

廖大神博客地址:https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/001432090171191d05dae6e129940518d1d6cf6eeaaa969000

Python協程爬取妹子圖(內有福利,你懂得~)