查看原文
其他

python爬虫(3):单页图片下载-网易"数读"信息图

苏克1900 高级农民工 2019-02-24

下载网络中的图片是件有意思的事,除了"右键另存为"这种方式以外,还可以用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篇文章的全部图片已下载完成,如果你需要可以公众号回复:"网易数读"下载。

本文完。

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存