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

不少网页代码里面并没有我们需要的内容,这是因为这些信息是通过 Ajax加载并渲染出来的。这时候就需要我们来分析网页的请求。

需要使用到

requests

BeautifulSoup + 正则表达式

MongoDB (Pymongo)

分析网页

我们需要抓取的链接是 : http://www.toutiao.com/search/?keyword=街拍 里面的 图集

XHR == XMLHttpRequest and Fetch

2017101915083975222624.png

这个是一个json文件,下面的网址才是实际的 URL(我们需要在代码里用到的)

我们需要从这个页面获取到json文件。

向下滑会有一个新的文件,这个文件通过 offset 递增来改变地址,每20个图集递增一次。

网页是通过 Ajax后台返回json数据然后渲染出来的。

获取 offset=0 的页面

import requests
from requests.exceptions import RequestException  
from urllib.parse import urlencode

def get_page_index(offset, keyword):
    data = {
        'offset': offset,
        'format': 'json',
        'keyword': keyword,
        'autoload': 'true',
        'count': '20',
        'cur_tab': 3
    }
    url = 'http://www.toutiao.com/search_content/?' + urlencode(data)
    try:
        response = requests.get(url)
        if response.status_code == 200:
            return response.text
        return None
    except RequestException:
        print('请求索引页出错')
        return None

def main():
    html = get_page_index(0, '街拍')
    print(html)


if __name__ == '__main__':
    main()

from requests.exceptions import RequestException 错误判断

结果如下

20171019150841345950571.png

url

url 是 Requests URL 里面的地址,不是浏览器里面的地址。

data 获取

20171019150839794822472.png

需要转换为字典的形式。

由于我们需要修改 offsetkeyword 所以这两个改成变量 存储在 data 里面。

获取每个页面 offset 里面的图集 URL

需要安装 json

pip3 install simplejson

完整代码

import requests
from requests.exceptions import RequestException
from urllib.parse import urlencode
import json

def get_page_index(offset, keyword):
    data = {
        'offset': offset,
        'format': 'json',
        'keyword': keyword,
        'autoload': 'true',
        'count': '20',
        'cur_tab': 3
    }
    url = 'http://www.toutiao.com/search_content/?' + urlencode(data)
    try:
        response = requests.get(url)
        if response.status_code == 200:
            return response.text
        return None
    except RequestException:
        print('请求索引页出错')
        return None

def parse_page_index(html):
    data = json.loads(html)
    if data and 'data' in data.keys():
        for item in data.get('data'):
            yield item.get('article_url')

def main():
    html = get_page_index(0, '街拍')
    for url in parse_page_index(html):
        print(url)

if __name__ == '__main__':
    main()

其中

data = json.loads(html) 把 html 变成结构化的 json 格式

if data and 'data in data.keys(): 判断 data 是在 data的 key 里面。

for item in data.get('data'): 做一个循环

yield item.get('article_url') 获取 key 为 article_url 的 数据,

yield 是一个参数(构造一个生成器)

结果如下

20171019150841355879419.png

有些 URL 不对,之后会过滤掉

获取单个页面的图片

20171025150889825072522.png

查看源代码 ,勾选 Preserve log,选择 All, 可以看到一个和网页链接是一样的文件,这个表示这个是网页原始请求

也可以直接 勾选 Font 边上的 Doc

完整代码

在获取图片链接的那一步出了问题,因为头条改了。

import json
import os
from urllib.parse import urlencode
import pymongo
import requests
from bs4 import BeautifulSoup
from requests.exceptions import ConnectionError
import re
from multiprocessing import Pool
from hashlib import md5
from json.decoder import JSONDecodeError
from config import *  #导入config.py 文件

client = pymongo.MongoClient(MONGO_URL, connect=False)
db = client[MONGO_DB]
# 链接mongodb


def get_page_index(offset, keyword):
    data = {
        'autoload': 'true',
        'count': 20,
        'cur_tab': 3,
        'format': 'json',
        'keyword': keyword,
        'offset': offset,
    }
    params = urlencode(data)
    base = 'http://www.toutiao.com/search_content/'
    url = base + '?' + params
    try:
        response = requests.get(url)
        if response.status_code == 200:
            return response.text
        return None
    except ConnectionError:
        print('Error occurred')
        return None


def download_image(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):
    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 parse_page_index(text):
    try:
        data = json.loads(text)
        if data and 'data' in data.keys():
            for item in data.get('data'):
                yield item.get('article_url')
    except JSONDecodeError:
        pass


def get_page_detail(url):
    try:
        response = requests.get(url)
        if response.status_code == 200:
            return response.text
        return None
    except ConnectionError:
        print('Error occurred')
        return None


def parse_page_detail(html, url):
    soup = BeautifulSoup(html, 'lxml')
    result = soup.select('title')
    title = result[0].get_text() if result else ''
    images_pattern = re.compile('var gallery = (.*?);', re.S)
    result = re.search(images_pattern, html)
    if result:
        data = json.loads(result.group(1))
        if data and 'sub_images' in data.keys():
            sub_images = data.get('sub_images')
            images = [item.get('url') for item in sub_images]
            for image in images: download_image(image)
            return {
                'title': title,
                'url': url,
                'images': images
            }


def save_to_mongo(result):
    if db[MONGO_TABLE].insert(result):
        print('Successfully Saved to Mongo', result)
        return True
    return False


def main(offset):
    text = get_page_index(offset, KEYWORD)
    urls = parse_page_index(text)
    for url in urls:
        html = get_page_detail(url)
        result = parse_page_detail(html, url)
        if result: save_to_mongo(result)


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

config.py 完整代码

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

GROUP_START = 1
GROUP_END = 20
KEYWORD='街拍'
Comments
Write a Comment