Python协程爬取妹子图(内有福利,你懂得~)

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python协程爬取妹子图(内有福利,你懂得~)相关的知识,希望对你有一定的参考价值。

项目说明:

  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协程爬取妹子图(内有福利,你懂得~)的主要内容,如果未能解决你的问题,请参考以下文章

爬虫协程爬取

python—多协程爬取斗鱼高颜值美女图片

python爬虫 asyncio aiohttp aiofiles 单线程多任务异步协程爬取图片

python3分别用多进程,多线程,协程爬取豆瓣top250数据(python经典编程案例)

利用协程多任务协程爬取前几页投诉网

利用协程多任务协程爬取前几页投诉网