python爬虫(3):单页图片下载-网易"数读"信息图
下载网络中的图片是件有意思的事,除了"右键另存为"这种方式以外,还可以用Python爬虫来自动下载。本文以信息图做得非常棒的网易"数读"栏目为例,介绍如何下载一个网页中的图片。
本文知识点:
单张图片下载
单页图片下载
Ajax技术介绍
1. 单张图片下载
以一篇最近比较热的涨房价的文章为例:暴涨的房租,正在摧毁中国年轻人的生活,从文章里随意挑选一张北京房租地图图片,通过Requests的content属性来实现单张图片的下载。
1import requests
2url = 'http://cms-bucket.nosdn.127.net/2018/08/31/df39aac05e0b417a80487562cdf6ca40.png'
3response = requests.get(url)
4with open('北京房租地图.jpg', 'wb') as f:
5 f.write(response.content)
5行代码就能将这张图片下载到电脑上。不只是该张图片,任意图片都可以下载,只要替换图片的url即可。
这里用到了Requests的content属性,将图片存储为二进制数据。至于,图片为什么可以用二进制数据进行存储,可以参考这个教程:
https://www.zhihu.com/question/36269548/answer/66734582
5行代码看起来很短,但如果只是下载一张图片显然没有必要写代码,"右键另存为"更快。现在,我们放大一下范围,去下载这篇文章中的所有图片。粗略数一下,网页里有超过15张图片,这时,如果再用"右键另存为"的方法,显然就比较繁琐了。下面,我们用代码来实现下载该网页中的所有图片。
2. 单页图片下载
2.1. Requests获取网页内容
首先,用堪称python"爬虫利器"的Requests库来获取该篇文章的html内容。
Requests库可以说是一款python爬虫的利器,它的更多用法,可参考下面的教程:
http://docs.python-requests.org/zh_CN/latest/index.html
https://cuiqingcai.com/2556.html
1import requests
2
3headers = {
4 'user-agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36'
5 }
6url = 'http://data.163.com/18/0901/01/DQJ3D0D9000181IU.html'
7
8response = requests.get(url,headers = headers)
9if response.status_code == 200:
10 # return response.text
11 print(response.text) # 测试网页内容是否提取成功ok
2.2. 解析网页内容
通过上面方法可以获取到html内容,接下来解析html字符串内容,从中提取出网页内的图片url。解析和提取url的方法有很多种,常见的有5种,分别是:正则表达式、Xpath、BeautifulSoup、CSS、PyQuery。任选一种即可,这里为了再次加强练习,5种方法全部尝试一遍。
首先,在网页中定位到图片url所在的位置,如下图所示:
从外到内定位url的位置:<p>节点-<a>节点-<img>节点里的src属性值
。
2.2.1. 正则表达式
1import re
2pattern =re.compile('<p>.*?<img alt="房租".*?src="(.*?)".*?style',re.S)
3 items = re.findall(pattern,html)
4 # print(items)
5 for item in items:
6 yield{
7 'url':item
8 }
运行结果如下:
1{'url': 'http://cms-bucket.nosdn.127.net/2018/08/31/425eca61322a4f99837988bb78a001ac.png'}
2{'url': 'http://cms-bucket.nosdn.127.net/2018/08/31/df39aac05e0b417a80487562cdf6ca40.png'}
3{'url': 'http://cms-bucket.nosdn.127.net/2018/08/31/d6cb58a6bb014b8683b232f3c00f0e39.png'}
4{'url': 'http://cms-bucket.nosdn.127.net/2018/08/31/88d2e535765a4ed09e03877238647aa5.png'}
5{'url': 'http://cms-bucket.nosdn.127.net/2018/09/01/98d2f9579e9e49aeb76ad6155e8fc4ea.png'}
6{'url': 'http://cms-bucket.nosdn.127.net/2018/08/31/7410ed4041a94cab8f30e8de53aaaaa1.png'}
7{'url': 'http://cms-bucket.nosdn.127.net/2018/08/31/49a0c80a140b4f1aa03724654c5a39af.png'}
8{'url': 'http://cms-bucket.nosdn.127.net/2018/08/31/3070964278bf4637ba3d92b6bb771cea.png'}
9{'url': 'http://cms-bucket.nosdn.127.net/2018/08/31/812b7a51475246a9b57f467940626c5c.png'}
10{'url': 'http://cms-bucket.nosdn.127.net/2018/08/31/8bcbc7d180f74397addc74e47eaa1f63.png'}
11{'url': 'http://cms-bucket.nosdn.127.net/2018/08/31/e593efca849744489096a77aafd10d3e.png'}
12{'url': 'http://cms-bucket.nosdn.127.net/2018/08/31/7653feecbfd94758a8a0ff599915d435.png'}
13{'url': 'http://cms-bucket.nosdn.127.net/2018/08/31/edbaa24a17dc4cca9430761bfc557ffb.png'}
14{'url': 'http://cms-bucket.nosdn.127.net/2018/08/31/f768d440d9f14b8bb58e3c425345b97e.png'}
15{'url': 'http://cms-bucket.nosdn.127.net/2018/08/31/3430043fd305411782f43d3d8635d632.png'}
16{'url': 'http://cms-bucket.nosdn.127.net/2018/08/31/111ba73d11084c68b8db85cdd6d474a7.png'}
2.2.2. Xpath语法
1from lxml import etree
2 parse = etree.HTML(html)
3 items = parse.xpath('*//p//img[@alt = "房租"]/@src')
4 print(items)
5 for item in items:
6 yield{
7 'url':item
8 }
结果同上。
2.2.3. CSS选择器
1soup = BeautifulSoup(html,'lxml')
2 items = soup.select('p > a > img') #>表示下级绝对节点
3 # print(items)
4 for item in items:
5 yield{
6 'url':item['src']
7 }
2.2.4. BeautifulSoup find_all方法
1soup = BeautifulSoup(html,'lxml')
2# 每个网页只能拥有一个<H1>标签,因此唯一
3item = soup.find_all(name='img',width =['100%'])
4for i in range(len(item)):
5 url = item[i].attrs['src']
6 yield{
7 'url':url
8 }
9 # print(pic) #测试图片链接ok
2.2.5. PyQuery
1from pyquery import PyQuery as pq
2data = pq(html)
3data2 = data('p > a > img')
4# print(items)
5for item in data2.items(): #注意这里和BeautifulSoup 的css用法不同
6 yield{
7 'url':item.attr('src')
8 # 或者'url':item.attr.src
9 }
以上用了5种方法提取出了该网页的url地址,任选一种即可。这里假设选择了第4种方法,接下来就可以下载图片了。提取出的网址是一个dict字典,通过dict的get方法调用里面的键和值。
1title = pic.get('title')
2url = pic.get('pic')
3# 设置图片编号顺序
4num = pic.get('num')
5
6# 建立文件夹
7if not os.path.exists(title):
8 os.mkdir(title)
9
10# 获取图片url网页信息
11response = requests.get(url,headers = headers)
12
13# 建立图片存放地址
14file_path = '{0}\{1}.{2}' .format(title,num,'jpg')
15# 文件名采用编号方便按顺序查看
16
17# 开始下载图片
18with open(file_path,'wb') as f:
19 f.write(response.content)
20 print('该图片已下载完成',title)
很快,15张图片就按着文章的顺序下载下来了。
将上述代码整理一下,增加一点异常处理和图片的标题、编号的代码以让爬虫更健壮,完整的代码如下所示:
2.3. 全部代码
1import requests
2from bs4 import BeautifulSoup
3import re
4import os
5from hashlib import md5
6from requests.exceptions import RequestException
7from multiprocessing import Pool
8from urllib.parse import urlencode
9
10headers = {
11 'user-agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36'
12 }
13
14def get_page():
15 # 下载1页
16 url = 'http://data.163.com/18/0901/01/DQJ3D0D9000181IU.html'
17
18 # 增加异常捕获语句
19 try:
20 response = requests.get(url,headers = headers)
21 if response.status_code == 200:
22 return response.text
23 # print(response.text) # 测试网页内容是否提取成功
24 except RequestException:
25 print('网页请求失败')
26 return None
27
28def parse_page(html):
29 soup = BeautifulSoup(html,'lxml')
30 # 获取title
31 title = soup.h1.string
32 # 每个网页只能拥有一个<H1>标签,因此唯一
33 item = soup.find_all(name='img',width =['100%'])
34 # print(item) # 测试
35
36 for i in range(len(item)):
37 pic = item[i].attrs['src']
38 yield{
39 'title':title,
40 'pic':pic,
41 'num':i # 图片添加编号顺序
42 }
43 # print(pic) #测试图片链接ok
44
45def save_pic(pic):
46 title = pic.get('title')
47 url = pic.get('pic')
48 # 设置图片编号顺序
49 num = pic.get('num')
50
51 if not os.path.exists(title):
52 os.mkdir(title)
53
54 # 获取图片url网页信息
55 response = requests.get(url,headers = headers)
56 try:
57 # 建立图片存放地址
58 if response.status_code == 200:
59 file_path = '{0}\{1}.{2}' .format(title,num,'jpg')
60 # 文件名采用编号方便按顺序查看,而未采用哈希值md5(response.content).hexdigest()
61 if not os.path.exists(file_path):
62 # 开始下载图片
63 with open(file_path,'wb') as f:
64 f.write(response.content)
65 print('该图片已下载完成',title)
66 else:
67 print('该图片%s 已下载' %title)
68 except RequestException as e:
69 print(e,'图片获取失败')
70 return None
71def main():
72 # get_page() # 测试网页内容是获取成功ok
73 html = get_page()
74 # parse_page(html) # 测试网页内容是否解析成功ok
75
76 data = parse_page(html)
77 for pic in data:
78 # print(pic) #测试dict
79 save_pic(pic)
80# 单进程
81if __name__ == '__main__':
82 main()
小结
上面通过爬虫实现下载一张图片延伸到下载一页图片,相比于手动操作,爬虫的优势逐渐显现。那么,能否实现多页循环批量下载更多的图片呢,当然可以,下一篇文章将进行介绍。
你也可以尝试一下,这里先放上"福利":网易"数读"栏目从2012年至今350篇文章的全部图片已下载完成,如果你需要可以公众号回复:"网易数读"下载。
本文完。