知足常乐

日常学习的记录与分享

python爬虫-ajax爬取今日头条街拍美图

1. 准备工作

请确保已经安装好requests库

2. 抓取分析

在抓取之前,首先要分析抓取的逻辑。打开今日头条的首页http://www.toutiao.com/
右上角有一个搜索入口,这里尝试抓取街拍美图,所以输入“街拍”二字搜索一下

《python爬虫-ajax爬取今日头条街拍美图》

我们右键查看网页源代码 或者 打开chrome调试工具 ,查看所有的网络请求
打开第一个网络请求,这个请求的URL就是当前的链接http://www.toutiao.com/search/?keyword=街拍,打开Preview选项卡查看Response Body

《python爬虫-ajax爬取今日头条街拍美图》

我们发现并没有页面中我们看到的任何关于美拍的关键字,
因此,可以初步判断这些内容是由Ajax加载,然后用JavaScript渲染出来的。接下来,我们可以切换到XHR过滤选项卡,查看一下有没有Ajax请求。

《python爬虫-ajax爬取今日头条街拍美图》

查看xrh中的信息 果然这才是我们请求的到的真实数据, 这就确定了这些数据确实是由Ajax加载的。

我们的目的是要抓取其中的美图,这里一组图就对应前面data字段中的一条数据。每条数据还有一个image_list字段,它是列表形式,这其中就包含了组图的所有图片列表

《python爬虫-ajax爬取今日头条街拍美图》

因此,我们只需要将列表中的url字段提取出来并下载下来就好了。每一组图都建立一个文件夹,文件夹的名称就为组图的标题。

接下来,就可以直接用Python来模拟这个Ajax请求,然后提取出相关美图链接并下载。但是在这之前,我们还需要分析一下URL的规律。

切换回Headers选项卡,观察一下它的请求URL和Headers信息

《python爬虫-ajax爬取今日头条街拍美图》
Request URL: https://www.toutiao.com/api/search/content/?aid=24&app_name=web_search&offset=20&format=json&keyword=%E8%A1%97%E6%8B%8D&autoload=true&count=20&en_qc=1&cur_tab=1&from=search_tab&pd=synthesis&timestamp=1552567884785

这是一个GET请求,请求URL的参数有offsetformatkeywordautoloadcountcur_tab。我们需要找出这些参数的规律

我们下拉请求 让他出现更多 找出规律

《python爬虫-ajax爬取今日头条街拍美图》

我们观察到只有offset和timestamp是变化的,显然offset是有效信息 我们观察他的偏移量由0,20,40递增这样我们就找出了规律 而且参数count也印证了每次加载会多出20个信息的猜想

3. 实战演练

我们可以去构造一个url去获取信息,我们可以指定keyword关键字来爬取不同类型的图片,但这里我们还是指定街拍,请求成功后我们将获取到的数据转化为json形式返回

def get_page(offset):
    params = {
        'aid': '24',
        'offset': offset,
        'format': 'json',
        #'keyword': '街拍',
        'autoload': 'true',
        'count': '20',
        'cur_tab': '1',
        'from': 'search_tab',
        'pd': 'synthesis'
    }
    base_url = 'https://www.toutiao.com/api/search/content/?keyword=%E8%A1%97%E6%8B%8D'
    url = base_url + urlencode(params)
    try:
        resp = requests.get(url)
        print(url)
        if 200  == resp.status_code:
            print(resp.json())
            return resp.json()
    except requests.ConnectionError:
        return None

下面我们实现一个图片解析的方法将data中标题名称和data下image-list中的图片取出,我们获取到的图片url信息实际为缩略图信息,我们需要去拿到实际完整的图片–》

以下面这个图片url为例http://p3-tt.bytecdn.cn/list/pgc-image/15324940498931739a24f4b 我们只要url编码中的list替换为origin即可,获取到标题和url返回一个生成器

def get_images(json):
    if json.get('data'):
        data = json.get('data')
        for item in data:
            if item.get('cell_type') is not None:
                continue
            title = item.get('title')
            images = item.get('image_list')
            for image in images:
                origin_image = re.sub("list", "origin", image.get('url'))
                print(image.get('url'))
                print(re.sub("list", "origin", image.get('url')))
                yield {
                    'image':  origin_image,
                    # 'iamge': image.get('url'),
                    'title': title
                }

print('succ')

我们获取到真实的url连接和标题后 我们需要在本地创建目录,在目录下创建图片

#将图片保存到本地
def save_image(item):
    #创建目录信息  img-> item.get('title')
    img_path = 'img' + os.path.sep + item.get('title')
    print('succ2')
    if not os.path.exists(img_path):
        os.makedirs(img_path)
    try:
        resp = requests.get(item.get('image'))
        if codes.ok == resp.status_code:
            #拼接文件名称文件,并用md5打散
            file_path = img_path + os.path.sep + '{file_name}.{file_suffix}'.format(
                file_name=md5(resp.content).hexdigest(),
                file_suffix='jpg')
            if not os.path.exists(file_path):
                print('succ3')
                with open(file_path, 'wb') as f:
                    #写入信息
                    f.write(resp.content)
                print('Downloaded image path is %s' % file_path)
                print('succ4')
            else:
                print('Already Downloaded', file_path)
    except requests.ConnectionError:
        print('Failed to Save Image,item %s' % item)

最后我们做一下分页,定义了分页的起始页数和终止页数,分别为GROUP_STARTGROUP_END,还利用了多线程的线程池,调用其map()方法实现多线程下载。

def main(offset):
    json = get_page(offset)
    for item in get_images(json):
        print(item)
        save_image(item)


GROUP_START = 0
GROUP_END = 7

if __name__ == '__main__':
    pool = Pool()
    groups = ([x * 20 for x in range(GROUP_START, GROUP_END + 1)])
    pool.map(main, groups)
    pool.close()
    pool.join()

我们先来看下效果

《python爬虫-ajax爬取今日头条街拍美图》

附上完整代码

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

#拼接url获取响应的ajax内容
def get_page(offset):
    params = {
        'aid': '24',
        'offset': offset,
        'format': 'json',
        #'keyword': '街拍',
        'autoload': 'true',
        'count': '20',
        'cur_tab': '1',
        'from': 'search_tab',
        'pd': 'synthesis'
    }
    base_url = 'https://www.toutiao.com/api/search/content/?keyword=%E8%A1%97%E6%8B%8D'
    url = base_url + urlencode(params)
    try:
        resp = requests.get(url)
        print(url)
        if 200  == resp.status_code:
            print(resp.json())
            return resp.json()
    except requests.ConnectionError:
        return None

#获取图片url和名称
def get_images(json):
    if json.get('data'):
        data = json.get('data')
        for item in data:
            if item.get('cell_type') is not None:
                continue
            title = item.get('title')
            images = item.get('image_list')
            for image in images:
                origin_image = re.sub("list", "origin", image.get('url'))
                #print(image.get('url'))
                #print(re.sub("list", "origin", image.get('url')))
                yield {
                    'image':  origin_image,
                    # 'iamge': image.get('url'),
                    'title': title
                }

print('succ')

#将图片保存到本地
def save_image(item):
    #创建目录信息  img-> item.get('title')
    img_path = 'img' + os.path.sep + item.get('title')
    print('succ2')
    if not os.path.exists(img_path):
        os.makedirs(img_path)
    try:
        resp = requests.get(item.get('image'))
        if codes.ok == resp.status_code:
            #拼接文件名称文件,并用md5打散
            file_path = img_path + os.path.sep + '{file_name}.{file_suffix}'.format(
                file_name=md5(resp.content).hexdigest(),
                file_suffix='jpg')
            if not os.path.exists(file_path):
                print('succ3')
                with open(file_path, 'wb') as f:
                    #写入信息
                    f.write(resp.content)
                print('Downloaded image path is %s' % file_path)
                print('succ4')
            else:
                print('Already Downloaded', file_path)
    except requests.ConnectionError:
        print('Failed to Save Image,item %s' % item)


def main(offset):
    json = get_page(offset)
    for item in get_images(json):
        print(item)
        save_image(item)


GROUP_START = 0
GROUP_END = 7

if __name__ == '__main__':
    pool = Pool()
    groups = ([x * 20 for x in range(GROUP_START, GROUP_END + 1)])
    pool.map(main, groups)
    pool.close()
    pool.join()
点赞
  1. Xiao_Cilang说道:

    url地址中,keyword必须放在第一个参数并且和后面的aid不能又’&‘相连,否则requests获取不到,这是为啥???

    1. 小黑说道:

      我这边是用params里面 直接拼进去的 讲道理一个get请求应该不会出这种问题

发表评论

电子邮件地址不会被公开。 必填项已用*标注