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

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

作者: 罗汉堂主 | 来源:发表于2020-03-17 11:40 被阅读0次

    上篇用一个微博博客的小例子来看了一下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数据采集-头条街拍美图采集

          本文链接:https://www.haomeiwen.com/subject/gtsdoctx.html