Python 協程爬取妹子圖~~~ async aiohttp scrapy ...
項目說明:
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))
協程的使用,大家移步到廖大神的哪裡學習下吧~~~,我就不講了...不然我要瘋了...我要看會電影,緩一會。