蒋振飞的博客 - 分析Ajax抓取今日头条街拍美图   
正在加载蒋振飞的博客...
V3.0
蒋振飞的博客

分析Ajax抓取今日头条街拍美图

发布时间: 2018年08月03日 发布人: 蒋振飞 热度: 703 ℃ 评论数: 0

声明:此篇文章主要是观看静觅教学视频后做的笔记,原教程地址https://cuiqingcai.com/

实现流程介绍

1.抓取索引页内容
    利用requests请求目标站点,得到索引网页HTML代码,返回结果

2.抓取详情页内容
    解析返回结果,得到详情页的链接,并进一步抓取详情页信息

3.下载图片与保存数据库
    将图片下载到本地,并把页面信息及图片URL保存至MongoDB

4.开启循环及多线程
    对多页内容遍历,开启多线程提高抓取速度

具体实现

1. 首先访问今日头条网站输入关键字来到索引页,我们需要通过分析网站来拿到进入详细页的url    
    经过观察可以发现每次滑动鼠标滚轮,新的标题链接就会被显示,所以可以发现其后台为Ajax请求,通过浏览器Network选项卡的XHR可以找到Ajax的链接,其为一个json数据,以搜索词街拍为例,其链接地址如下:

https://www.toutiao.com/search_content/?offset=0&format=json&keyword=%E8%A1%97%E6%8B%8D&autoload=true&count=20&cur_tab=1&from=search_tab

2.通过点击查看Query String Parameters中的内容,可以看到一些类似字典的数据,所以这是一会需要通过urlencode来转码拼接成最终访问的地址

offset: 0
format: json
keyword: 街拍
autoload: true
count: 20
cur_tab: 1
from: search_tab

3.刷取Ajax内容
    随着向下滑动滚动条显示更多的图片索引,会发现刷出了很多新的ajax请求,通过这个我们可以知道我们之后可以通过改变offset参数来获取不同的拿到不同的索引界面,从而获得不同的图集详细页url。开始只需实现一个offset参数的爬取,最后通过进程池Pool来创建实现多进程爬取不同offset参数的URL,加快爬取速度。

4.找出Ajax请求的目标地址
    接下来就是分析查找图集详细页的代码,来找到图片的url,这个图片url隐藏的比较深,都在JS代码中,所以不能使用BeautifulSoup和PyQuery来解析了,只能通过正则解析,使用正则解析要注意匹配规则一定要写对。刷新页面后,自己基础比较差,找了好久换了火狐浏览器,又换回谷歌,最后在Network选项卡的Doc发现下面这个链接,而图片地址就藏在gallery: JSON.parse里。

https://www.toutiao.com/a6585311263927042573/

5.代码实现
    代码直接进行展示吧,需要的注释我已经写在代码里了,先编辑一个config.py的文件,里面设置了代码中用到的变量。

MONGO_URL = 'localhost'
MONGO_DB = 'toutiao'
MONGO_TABLE = 'toutiao'

GROUP_START = 1
GROUP_END = 20
KEYWORD='街拍'
#!/usr/bin/env python
# coding=utf-8

from urllib.parse import urlencode
from requests.exceptions import ConnectionError
from bs4 import BeautifulSoup
from json.decoder import JSONDecodeError
from hashlib import md5
from config import *
from multiprocessing import Pool
import requests
import json
import re
import os
import pymongo

client = pymongo.MongoClient(MONGO_URL, connect=False)
db = client[MONGO_DB]

def get_page_index(url, headers):
    """
        作用:返回页面源码
        url:请求地址
        headers:请求头信息
    """
    try:
        response = requests.get(url, headers=headers)
        # 判断是否访问成功
        if response.status_code == 200:
            return response.text
    except ConnectionError:
        print('Erroe occured')
        return None

def parse_page_index(html):
    """
        作用:解析出标题URL地址
        html:网页源码
    """
    try:
        # 将数据转为json格式
        data = json.loads(html)
        # print(data)

        # 判断data是否为空,以及data字典中是否有data这个键
        if data and 'data' in data.keys():
            for item in data.get('data'):
                if item.get('article_url'):
                    yield item.get('article_url')
    except JSONDecodeError:
        pass

def get_page_detail(url, headers):
    """
        作用:返回标题URL网页源码
        url:标题URL地址
        headers:请求头信息
    """
    try:
        response = requests.get(url, headers=headers)
        # 判断是否访问成功
        if response.status_code == 200:
            return response.text
    except ConnectionError:
        print('Error occured')
        return None

def parse_page_detail(html, url):
    """
        作用:解析标题URL地址的每个图片链接
        html:标题URL网页源码
        url:标题URL地址
    """
    # 利用BeautifulSoup找到title的文本
    soup = BeautifulSoup(html, 'lxml') 
    title = soup.title.text
    # 利用正则找到每个下载图片的地址
    images_pattern = re.compile('gallery: JSON.parse\("(.*)"\)', re.S)
    result = images_pattern.search(html)
    # print(result)
    if result:
        data = json.loads(result.group(1).replace('\\', ''))
        # 提取出sub_images键的键值
        if data and 'sub_images' in data.keys():
            sub_images = data.get('sub_images')
            # 使用列表生成式拿到每个图片URL
            images = [item.get('url') for item in sub_images]
            for image in images: 
                # 下载图片
                download_image(image)
                # 将return的结果保存至MongoDB中
                return {
                    'title': title,
                    'url': url,
                    'images': images
                }

def download_image(url):
    """
        作用:返回图片URL源码
        url:图片URL地址
    """
    print('Downloading', url)
    try:
        response = requests.get(url)
        # 判断是否访问成功
        if response.status_code == 200:
            save_image(response.content)
            return None
    except ConnectionError:
        return None

def save_image(content):
    """
        作用:保存图像文件
        content:图像二进制数据
    """
    # 使用md5加密内容,生成图像名称
    file_path = '{0}/{1}.{2}'.format(os.getcwd(), md5(content).hexdigest(), 'jpg')
    print(file_path)
    # 判断该文件名是否存在
    if not os.path.exists(file_path):
        with open(file_path, 'wb') as f:
            f.write(content)
            f.close()

def save_to_mongo(result):
    """
        作用:保存数据至MongoDB数据库
        result:包括图片标题,请求地址,图像地址
    """
    if db[MONGO_TABLE].insert(result):
        print('Successfully Saved to Mongo', result)
        return True
    return False

def jiepai_Spider(offset):
    """
        作用:整个爬虫调度器
        offset:位置参数
    """
    headers = {
        'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.62 Safari/537.36'
    }

    data = {
        "offset": offset,
        "format": "json",
        "keyword": "街拍",
        "autoload": "true",
        "count": "20",
        "cur_tab": "1",
        "from": "search_tab"
    }
    # 通过urlencode构造请求URL
    url = 'https://www.toutiao.com/search_content/' + '?' + urlencode(data)

    # 测试url
    # print(url)

    # 获取页面源码
    html = get_page_index(url, headers)

    # 解析HTML,获得链接地址
    for url in parse_page_index(html):
        # print(url)
        # 获得每个链接地址的HTML
        html = get_page_detail(url, headers)
        result = parse_page_detail(html, url)

        # 判断result是否为空,保存至MongoDB数据库中
        if result: 
            save_to_mongo(result)


if __name__ == "__main__":
    # 创建进程池
    pool = Pool()
    groups = ([x * 20 for x in range(GROUP_START, GROUP_END + 1)])
    pool.map(jiepai_Spider, groups)
    pool.close()
    # 等待pool中所有子进程执行完成,必须放在close语句之后
    pool.join()

总结思考

1.在利用正则进行匹配的时候如果原文有"(" ")",这类符号时那么你在进行正则表达式书写的时候应该在前面加'\'。按理应该也可以使用原始字符串r,可是我用完最后在匹配的时候返回的是None,留个疑问。

pattern = re.compile('gallery: JSON\.parse\("(.*?)"\),', re.S)

2. db = client[MONGO_DB]这里应该是方括号而不是 ( ),否则无法正常访问数据库。

3. 在Google浏览器中找不到图片url,然后使用的是火狐浏览器来回查看。

4.完整源码地址:https://github.com/XiaoFei-97/toutiao_Spider-Ajax

打赏 蒋振飞

取消

感谢您的支持,我会继续努力的!

扫码支持
一分也是爱     一块不嫌多

点击 支付宝 或 微信 打赏蒋振飞

打开支付宝扫一扫,即可进行扫码打赏哦

评论列表