上篇用一个微博博客的小例子来看了一下Ajax异步加载数据的采集,为了加深一下印象,这篇特意选出了一个主题“街拍美图”,这里注意一下,不是美女图(做爬虫的可能不只是广大男同胞),上篇有美食,这篇有美图,相信通过这两次的采集小例子,对Ajax异步加载数据的采集会印象深刻了吧。
话不多说,开始正题。。。
1.分析
有了上次Ajax的简单介绍,这里就不再多说了,这次我们的主题是“采集今日头条的街拍美图”,无疑是要从头条开始了,先进入头条首页,搜索栏输入“街拍美图”:
进入页面后按
F12
,打开开发者工具,切换到Network
,然后刷新页面,之后信息会加载出来,看图:下面我们看到加载的很多信息,不难发现,第二条信息就是我们点搜索之后加载的页面,切换到
Response
看下响应,ctri+F
搜索页面上第一条信息的标题,之后看到搜索的结果为0,这样我们可以判定,这条信息以及后面的信息都是Ajax异步加载出来的数据:判断是Ajax异步加载的数据之后,我们可以切换到
XHR
选项看一下请求,正好有一个,然后选择preview看一下响应数据,发现正好使我们所需要的:然后我们滚动滚轮,就会继续加载一些新的数据,我这里刷出来了共七条,下面我们就根据这七个Ajax请求来分析一下他的请求跟响应的过程:
首先这是个get请求,然后通过对比发现,请求参数中变化的就两个参数:
offset
, timestamp
,其中offset
是从0到120变化,步长是20,可以猜测是响应数据的条数,timestamp
就是时间戳了:2.实现
大概的分析了一下之后,我们就开始正式采集了,老样子,先把基本框架搭好:
import datetime
import time
import random as rd
import requests
class ToutSpider(object):
def __init__(self):
self._headers = {
'Accept-Encoding': 'gzip, deflate, br',
'Accept-Language': 'zh-CN,zh;q=0.9',
'Content-Type': 'application/x-www-form-urlencoded',
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.87 Safari/537.36',
'X-Requested-With': 'XMLHttpRequest',
}
def get_response(self, req_url, params_dict=None):
"""
请求
:param req_url:
:param params_dict:
:return:
"""
if params_dict:
response = requests.get(req_url, params=params_dict, headers=self._headers)
else:
response = requests.get(req_url, headers=self._headers)
if response.status_code == 200:
return response.content.decode('utf-8')
return None
def run(self):
pass
if __name__ == '__main__':
tt_spider = ToutSpider()
tt_spider.run()
然后拼接请求参数:
def run(self, offset, timestamp):
"""
主函数
:return:
"""
params_dict = {
"aid": 24,
"app_name": "web_search",
"offset": offset,
"format": "json",
"keyword": "街拍美图",
"autoload": "true",
"count": 20,
"en_qc": 1,
"cur_tab": 1,
"from": "search_tab",
"pd": "synthesis",
"timestamp": timestamp,
}
start_url = "https://www.toutiao.com/api/search/content/"
# 1.发出请求,获取响应
response = self.get_response(start_url, params_dict)
print(response)
if __name__ == '__main__':
tt_spider = ToutSpider()
# 这里的时间戳我们之前解析过,这里就直接拿过来
dtime = datetime.datetime.now()
un_time = time.mktime(dtime.timetuple())
timestamp = int(f'{int(un_time)}{rd.randint(100, 999)}')
# 我们还要定义好offset,暂时先采集40条的,那offset应该这样定义
for i in range(2):
print(f'{"=" * 30}开始采集第{i * 20}条到第{i * 20 + 20}条数据')
offset = i * 20
tt_spider.run(offset, timestamp)
看下结果,数据量太多,就贴个图吧,前面的count
已经显示出每次请求都响应回了20条数据:
看了一下响应值的情况,目前发现共响应了五种类型的数据,是用
ala_src
,app_info
跟query_type
来控制的,所以还需要做一下简单的判断,看下具体方法:
def data_parse(self, response):
"""
解析内容并返回
:param response:
:return:
"""
data_list = response['data']
for i in range(len(data_list)):
if data_list[i]['app_info'] == None:
pass
elif data_list[i]['app_info']['query_type'].startswith('Search'):
if 'Internal' in data_list[i]['app_info']['query_type']:
yield {"image_title": data_list[i]['title'], "image_list": [img['url'] for img in data_list[i]['image_list']]}
else:
yield {"image_title": data_list[i]['title'], "image_list": data_list[i]['display']['info']['images']}
else:
if data_list[i]['ala_src'] == 'news':
for merge_article in data_list[i]['merge_article']:
yield {"image_title": merge_article['title'], "image_list": [img['url'] for img in merge_article['image_list']]}
else:
for merge_result in data_list[i]['display']['results']:
yield {"image_title": merge_result['text'], "image_list": [merge_result['img_url']]}
接下来就是数据存储了,我们把处理完的数据存到文件中,这里有几个地方需要注意下:第一个,因为我们创建文件夹是用头条采到的title
,:
在创建文件的时候会报错,所以需要处理下;第二个,图片命名可以使用图片内容的MD5值,这样可以达到去重的效果。,具体方法如下:
def save_image(self, image_data):
"""
图片存储
:param image_data:
:return:
"""
file_path = image_data['image_title'].replace(':', ':') if ':' in image_data['image_title'] else image_data['image_title']
if not os.path.exists(f"image/{file_path}"):
os.makedirs(f"image/{file_path}")
for img_url in image_data['image_list']:
try:
response = requests.get(img_url)
img_path = f"image/{file_path}/{md5(response.content).hexdigest()}.jpg"
if not os.path.exists(img_path):
with open(img_path, 'wb') as f:
f.write(response.content)
except Exception as e:
print(f"save image is error: {str(e)}")
最后贴下main
:
if __name__ == '__main__':
tt_spider = ToutSpider()
# 这里的时间戳我们之前解析过,这里就直接拿过来
dtime = datetime.datetime.now()
un_time = time.mktime(dtime.timetuple())
timestamp = int(f'{int(un_time)}{rd.randint(100, 999)}')
# 我们还要定义好offset,暂时先采集40条的,那offset应该这样定义
for i in range(2):
print(f'{"=" * 30}开始采集第{i * 20}条到第{i * 20 + 20}条数据')
offset = i * 20
tt_spider.run(offset, timestamp)
time.sleep(10)
我们看下采集的结果;
3.结语
到现在,我们完整的头条采集美图的小例子就做完了,流程方法也是挺简单的,不过之间的过程也是踩了很多坑。有兴趣的兄弟姐妹可以去尝试一下。。。