爬虫实战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.结语

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容

  • 不知道大家有没有遇到这种情况:当我们requests发出请求采集页面信息的时候,得到的结果肯能会跟在浏览器中看到的...
    罗汉堂主阅读 516评论 0 0
  • 最近一直在忙毕业论文,没有时间更新公众号文章,不过后台一直有人和我交流一些Python技术的问题,我感到非常欣慰。...
    叫我小包总阅读 507评论 1 1
  • 站在海岛的顶峰 看着旭日东升 无限绚丽在天海之间 俯瞰村舍 炊烟袅袅 男人们在准备出海 孩童们打扫街道和院落 一切...
    俭以养德文以载道阅读 432评论 6 5
  • 为了同一个梦想来自五湖四海的老师相聚一堂,听时老师讲课,她的声音好像能赋予了我们能量,听着听着我们情不自禁的就...
    bcd1030177c4阅读 172评论 0 0