查看原文
其他

python爬虫(4):多页图片批量下载-澎湃网信息图

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

澎湃网文章的质量不错,它的"美数课"栏目的信息图做得也很好。图片干货多还能带来ppt和图表制作的技巧。为了更方便浏览所有文章图片,通过分析Ajax爬取栏目至今所有信息图的图片。

摘要: 上一篇文章介绍了单页图片的爬取,可以点击下面链接回顾:python爬虫(3):单页图片下载-网易"数读"信息图

但是当爬取多页时,难度会增加,同时,前几篇爬虫文章中的网站有一个明显的特点是:可以通过点击鼠标实现网页的翻页,并且url会发生相应的变化。除了此类网站以外,还有一类非常常见的网站特点是:没有"下一页"这样的按钮,而是"加载更多"或者会不断自动刷新从而呈现出更多的内容,同时网页url也不发生变化。这种类型的网页通常采用的是Ajax技术,要抓取其中的网页内容需要采取一定的技巧。本文以信息图做得非常棒的澎湃"美数课"为例,抓取该栏目至今所有文章的图片。  

栏目网址:https://www.thepaper.cn/list_25635


本文知识点:  

  • Ajax知识

  • 多页图片爬取

1. Ajax知识

在该主页上尝试不断下拉,会发现网页不断地加载出新的文章内容来,而并不需要通过点击"下一页"来实现,而且网址url也保持不变。也就是说在同一个网页中通过下拉源源不断地刷新出了网页内容。这种形式的网页在今天非常常见,它们普遍是采用了Ajax技术。

Ajax 全称是 Asynchronous JavaScript and XML(异步 JavaScript 和 XML)。
它不是一门编程语言,而是利用 JavaScript 在保证页面不被刷新、页面链接不改变的情况下与服务器交换数据并更新部分网页的技术。

Ajax更多参考:  
https://germey.gitbooks.io/python3webspider/content/6.2-Ajax%E5%88%86%E6%9E%90%E6%96%B9%E6%B3%95.html

采用了Ajax的网页和普通的网页有一定的区别,普通网页的爬虫代码放在这种类型的网页上就行不通了,必须另辟出路。下面我们就来尝试一下如何爬取网易"数读"所有的文章。

主页右键-检查,然后按f5刷新,会弹出很多链接文件。鼠标上拉回到第一个文件:list_25635,在右侧按ctrl+f搜索一下第一篇文章的标题:"娃娃机生意经",可以看到在html网页中找到了对应的源代码。

接着,我们拖动下拉鼠标,显示出更多文章。然后再次搜索一篇文章的标题:"金砖峰会",会发现搜不到相应的内容了。是不是感觉很奇怪?

其实,这里就是用了Ajax的技术,和普通网页翻页是刷新整个网页不同,这种类型网页可以再保持url不变的前提下只刷新部分内容。这就为我们进行爬虫带来了麻烦。因为,我们通过解析网页的url:https://www.thepaper.cn/list_25635只能爬取前面部分的内容而后面通过下拉刷新出来的内容是爬取不到的。这显然不完美,那么怎么才能够爬取到后面不断刷新出来的网页内容呢?

2. url分析

我们把右侧的选项卡从ALL切换到Network,然后按再次按f5刷新,可以发现Name列有4个结果。选择第3个链接打开并点击Response,通过滑动可以看到一些文本内容和网页中的文章标题是一一对应的。比如第一个是:娃娃机生意经|有没有好奇过抓娃娃机怎么又重新火起来了?,一直往下拖拽可以看到有很多篇文章。此时,再切换到headers选项卡,复制Request URL后面的链接并打开,会显示一部分文章的标题和图片内容。数一下的话,可以发现一共有20个文章标题,也就是对应着20篇文章。

这个链接其实和上面的list_25635链接的内容是一致的。这样看来,好像发现不了什么东西,不过不要着急。


接下来,回到Name列,尝试滚动下拉鼠标,会发现弹出好几个新的开头为load_index的链接来。选中第一个load_index的链接,点击Response查看一下html源代码,尝试在网页中搜索一下:十年金砖峰这个文章的标题,惊奇地发现,在网页中找到了对于的文章标题。而前面,我们搜索这个词时,是没有搜索到的。

这说明了什么呢?说明十年金砖峰这篇文章的内容不在第一个list_25635链接中,而在这个load_index的链接里。鼠标点击headers,复制Request URL后面的链接并打开,就可以再次看到包括这篇文章在内的新的20篇文章。

是不是发现了点了什么?接着,我们继续下拉,会发现弹出更多的load_index的链接。再搜索一个标题:地图湃|海外港口热,可以发现在网页中也同样找到了文章标题。

回到我们的初衷:下载所有网页的图片内容。那么现在就有解决办法礼:一个个地把出现的这些url网址中图片下载下来就大功告成了。

好,我们先来分析一下这些url,看看有没有相似性,如果有很明显的相似性,那么就可以像普通网页那样,通过构造翻页页数的url,实现for循环就可以批量下载所有网页的图片了。复制前3个链接如下:

1https://www.thepaper.cn/load_index.jsp?nodeids=25635&topCids=&pageidx=2&isList=true&lastTime=1533169319712  
2https://www.thepaper.cn/load_index.jsp?nodeids=25635&topCids=&pageidx=3&isList=true&lastTime=1528625875167  
3https://www.thepaper.cn/load_index.jsp?nodeids=25635&topCids=&pageidx=4&isList=true&lastTime=1525499007926

发现pageidx键的值呈现规律的数字递增变化,看起来是个好消息。但同时发现后面的lastTime键的值看起来是随机变化的,这个有没有影响呢? 来测试一下,复制第一个链接,删掉&lastTime=1533169319712这一串字符,会发现网页一样能够正常打开,就说明着一对参数不影响网页内容,那就太好了。我们可以删除掉,这样所有url的区别只剩pageidx的值了,这时就可以构造url来实现for循环了。构造的url形式如下:

1https://www.thepaper.cn/load_index.jsp?nodeids=25635&pageidx=2
2https://www.thepaper.cn/load_index.jsp?nodeids=25635&pageidx=3
3https://www.thepaper.cn/load_index.jsp?nodeids=25635&pageidx=4

同时,尝试把数字2改成1并打开链接看看会有什么变化,发现呈现的内容就是第1页的内容。这样,我们就可以从第一页开始构造url循环了。  
https://www.thepaper.cn/load_index.jsp?nodeids=25635&pageidx=1  

既然确定了首页,那么也要相应地确定一下尾页。很简单,我们把数字改大然后打开链接看是否有内容即可。比如改为**10 **,打开发现有内容显示,很好。接着,再改为30,发现没有内容了。说明该栏目的页数介于这两个数之间,尝试几次后,发现25是最后一个有内容的网页,也意味着能够爬取的页数一共是25页。

确定了首页和尾页后,下面我们就可以开始构造链接,先爬取第一篇文章网页里的图片(这个爬取过程,我们上一篇爬取网易"数读"已经尝试过了),然后爬取这一整页的图片,最后循环25页,爬取所有图片,下面开始吧。

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
14# 1 获取索引界面网页内容
15def get_page_index(i):
16    # 下载1页
17    # url = 'https://www.thepaper.cn/newsDetail_forward_2370041'
18
19    # 2下载多页,构造url
20    paras = {
21        'nodeids'25635,
22        'pageidx': i
23    }
24    url = 'https://www.thepaper.cn/load_index.jsp?' + urlencode(paras)
25
26    response = requests.get(url,headers = headers)
27    if response.status_code == 200:
28        return response.text
29        # print(response.text)  # 测试网页内容是否提取成功ok
30
31# 2 解析索引界面网页内容
32def parse_page_index(html):
33    soup = BeautifulSoup(html,'lxml')
34
35    # 获取每页文章数
36    num = soup.find_all(name = 'div',class_='news_li')
37    for i in range(len(num)):
38        yield{
39        # 获取title
40        'title':soup.select('h2 a')[i].get_text(),
41        # 获取图片url,需加前缀
42        'url':'https://www.thepaper.cn/' + soup.select('h2 a')[i].attrs['href']
43        # print(url)  # 测试图片链接
44        }
45
46# 3 获取每条文章的详情页内容
47def get_page_detail(item):
48    url = item.get('url')
49    # 增加异常捕获语句
50    try:
51        response = requests.get(url,headers = headers)
52        if response.status_code == 200:
53            return response.text
54            # print(response.text)  # 测试网页内容是否提取成功
55    except RequestException:
56        print('网页请求失败')
57        return None
58
59# 4 解析每条文章的详情页内容
60def parse_page_detail(html):
61    soup = BeautifulSoup(html,'lxml')
62    # 获取title
63    if soup.h1:  #有的网页没有h1节点,因此必须要增加判断,否则会报错
64        title = soup.h1.string
65        # 每个网页只能拥有一个<H1>标签,因此唯一
66        items = soup.find_all(name='img',width =['100%','600'])
67        # 有的图片节点用width='100%'表示,有的用600表示,因此用list合并选择
68        # https://blog.csdn.net/w_xuechun/article/details/76093950
69        # print(items) # 测试返回的img节点ok
70        for i in range(len(items)):
71            pic = items[i].attrs['src']
72            # print(pic) #测试图片链接ok
73            yield{
74            'title':title,
75            'pic':pic,
76            'num':i  # 图片添加编号顺序
77            }
78# 5 下载图片
79def save_pic(pic):
80    title = pic.get('title')
81    # 标题规范命名:去掉符号非法字符| 等
82     title = re.sub('[\/:*?"<>|]','-',title).strip()
83    url = pic.get('pic')
84    # 设置图片编号顺序
85    num = pic.get('num')
86
87    if not os.path.exists(title):
88        os.mkdir(title)
89
90    # 获取图片url网页信息
91    response = requests.get(url,headers = headers)
92    try:
93    # 建立图片存放地址
94        if response.status_code == 200:
95            file_path = '{0}\{1}.{2}' .format(title,num,'jpg')
96            # 文件名采用编号方便按顺序查看,而未采用哈希值md5(response.content).hexdigest()
97            if not os.path.exists(file_path):
98                # 开始下载图片
99                with open(file_path,'wb'as f:
100                    f.write(response.content)
101                    print('文章"{0}"的第{1}张图片下载完成' .format(title,num))
102            else:
103                print('该图片%s 已下载' %title)
104    except RequestException as e:
105        print(e,'图片获取失败')
106        return None
107
108def main(i):
109    # get_page_index(i) # 测试索引界面网页内容是否获取成功ok
110
111    html = get_page_index(i)
112    data = parse_page_index(html)  # 测试索引界面url是否获取成功ok
113    for item in data:
114        # print(item)  #测试返回的dict
115        html = get_page_detail(item)
116        data = parse_page_detail(html)
117        for pic in data:
118            save_pic(pic)
119
120# 单进程
121if __name__ == '__main__':
122    for i in range(1,26):
123        main(i)
124
125# 多进程
126if __name__ == '__main__':
127    pool = Pool()
128    pool.map(main,[i for i in range(1,26)])
129    pool.close()
130    pool.join()

结果:


文章代码和栏目从2015年至今437篇文章共1509张图片资源,可在下方链接中得到:https://github.com/makcyun/web_scraping_with_python

本文完。


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

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