爬虫实战1.4.2 Ajax数据采集-头条街拍美图采集

上篇用一个微博博客的小例子来看了一下Ajax异步加载数据的采集,为了加深一下印象,这篇特意选出了一个主题“街拍美图”,这里注意一下,不是美女图(做爬虫的可能不只是广大男同胞),上篇有美食,这篇有美图,相信通过这两次的采集小例子,对Ajax异步加载数据的采集会印象深刻了吧。
话不多说,开始正题。。。

1.分析

有了上次Ajax的简单介绍,这里就不再多说了,这次我们的主题是“采集今日头条的街拍美图”,无疑是要从头条开始了,先进入头条首页,搜索栏输入“街拍美图”:

搜索

进入页面后按F12,打开开发者工具,切换到Network,然后刷新页面,之后信息会加载出来,看图:
信息加载

下面我们看到加载的很多信息,不难发现,第二条信息就是我们点搜索之后加载的页面,切换到Response看下响应,ctri+F 搜索页面上第一条信息的标题,之后看到搜索的结果为0,这样我们可以判定,这条信息以及后面的信息都是Ajax异步加载出来的数据:
判断是否Ajax加载数据

判断是Ajax异步加载的数据之后,我们可以切换到XHR选项看一下请求,正好有一个,然后选择preview看一下响应数据,发现正好使我们所需要的:
异步加载数据

然后我们滚动滚轮,就会继续加载一些新的数据,我这里刷出来了共七条,下面我们就根据这七个Ajax请求来分析一下他的请求跟响应的过程:
滚动出现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_infoquery_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.结语

到现在,我们完整的头条采集美图的小例子就做完了,流程方法也是挺简单的,不过之间的过程也是踩了很多坑。有兴趣的兄弟姐妹可以去尝试一下。。。

你可能感兴趣的:(爬虫实战1.4.2 Ajax数据采集-头条街拍美图采集)