以今日头条为例分析Ajax请求抓取网页数据。本次抓取今日头条的街拍关键字对应的图片,并保存到本地

一,分析

  打开今日头条主页,在搜索框中输入街拍二字,打开开发者工具,发现浏览器显示的数据不在其源码里面。这样可以出初步判断这些内容是由

Ajax加载,然后使用JavaScript渲染出来的。

        爬虫—分析Ajax爬取今日头条图片

  切换到XHR过滤选项卡,查看其Ajax请求。点击其中一条进去,进入data展开,发现其中一个title字段对应的值正好是页面中的某条数据的标题。再查看其他数据,正好也是一一对应的,这说明这些数据确实是由Ajax加载的。

         爬虫—分析Ajax爬取今日头条图片

  

  本次的目的是抓取其中的图片内容,data中每个元素就是一篇文章,元素中的image_list字段包含了该文章的图片内容。它是一个列表形式,包含了所有的图片列表。我们只需要将列表中的url字段下载下来就好了,每篇文章都创建一个文件夹,文件夹名称即文章标题。

          爬虫—分析Ajax爬取今日头条图片

  在使用Python爬取之前还需要分析一下URL的规律。切换到Headers选项卡,查看Headers信息。可以看到,这是一个GET请求,请求的参数有aid,app_name,offset,format,keyword,autoload,count,en_qc,cur_tab,from,pd,timestamp。继续往下滑动,多加载一些数据,找出其中的规律。

        爬虫—分析Ajax爬取今日头条图片

  经过观察,可以发现变化的参数只有offset,timestamp。第一次请求的offset的值为0,第二次为20,第三次为40,key推断出这个offset就是偏移量,count为每次请求的数据量,而timestamp为时间戳。这样一来,我们就可以使用offset参数控制分页了,通过模拟Ajax请求获取数据,最后将数据解析后下载即可。

二,爬取

  刚才已经分析完了整个Ajax请求,接下来就是使用代码来实现这个过程。

# _*_ coding=utf-8 _*_

import requests
import time
import os
from hashlib import md5
from urllib.parse import urlencode
from multiprocessing.pool import Pool


def get_data(offset):
    """
    构造URL,发送请求
    :param offset:
    :return:
    """
    timestamp = int(time.time())
    params = {
        'aid': '24',
        'app_name': 'web_search',
        'offset': offset,
        'format': 'json',
        'autoload': 'true',
        'count': '20',
        'en_qc': '1',
        'cur_tab': '1',
        'from': 'search_tab',
        'pd': 'synthesis',
        'timestamp': timestamp
    }

    base_url = 'https://www.toutiao.com/api/search/content/?keyword=%E8%A1%97%E6%8B%8D'
    url = base_url + urlencode(params)
    try:
        res = requests.get(url)
        if res.status_code == 200:
            return res.json()
    except requests.ConnectionError:
        return '555...'


def get_img(data):
    """
    提取每一张图片连接,与标题一并返回,构造生成器
    :param data:
    :return:
    """
    if data.get('data'):
        page_data = data.get('data')
        for item in page_data:
            # cell_type字段不存在的这类文章不爬取,它没有title,和image_list字段,会出错
            if item.get('cell_type') is not None:
                continue
            title = item.get('title').replace(' |', ' ')    # 去掉某些可能导致文件名错误而不能创建文件的特殊符号,根据具体情况而定
            imgs = item.get('image_list')
            for img in imgs:
                yield {
                    'title': title,
                    'img': img.get('url')
                }


def save(item):
    """
    根据title创建文件夹,将图片以二进制形式写入,
    图片名称使用其内容的md5值,可以去除重复的图片
    :param item:
    :return:
    """
    img_path = 'img' + '/' + item.get('title')
    if not os.path.exists(img_path):
        os.makedirs(img_path)
    try:
        res = requests.get(item.get('img'))
        if res.status_code == 200:
            file_path = img_path + '/' + '{name}.{suffix}'.format(
                name=md5(res.content).hexdigest(),
                suffix='jpg')
            if not os.path.exists(file_path):
                with open(file_path, 'wb') as f:
                    f.write(res.content)
                print('Successful')
            else:
                print('Already Download')
    except requests.ConnectionError:
        print('Failed to save images')


def main(offset):
    data = get_data(offset)
    for item in get_img(data):
        print(item)
        save(item)


START = 0
END = 10
if __name__ == "__main__":
    pool = Pool()
    offsets = ([n * 20 for n in range(START, END + 1)])
    pool.map(main, offsets)
    pool.close()
    pool.join()

  这里定义了起始页START和结束页END,可以自定义设置。然后利用多进程的进程池,调用map()方法实现多进程下载。运行之后发现图片都报存下来了。

                                        爬虫—分析Ajax爬取今日头条图片