python爬虫从小白到高手 Day2 动态页面的爬取

今天我们说说动态页面的抓取,动态页面的概念不是说网页上的内容是活动的,而是刷新的内容由Ajax加载,页面的URL没有变化,具体概念问度娘。
就以男人都喜欢的美女街拍为例,对象为今日头条。
chrome打开今日头条 ->搜索
https://www.toutiao.com/search/?keyword=街拍
开发者工具->network选项卡
图2-1

2-1.png

很多条目,各种请求,但Ajax其实有其特殊的请求类型,它叫作xhr。在图6-3中,我们可以发现一个名称以getIndex开头的请求,其Type为xhr,这就是一个Ajax请求。用鼠标点击这个请求,可以查看这个请求的详细信息。

图2-2
2-2.png

选中这个xhr请求后,我们可以看到Request Headers中X-Requested-With:XMLHttpRequest,这就标记了此请求是Ajax请求。
点击一下Preview,即可看到响应的内容,它是JSON格式的。这里Chrome为我们自动做了解析,点击箭头即可展开和收起相应内容,初步分析这里返回的是页面上显示出来的前二十条信息。

图2-3
2-3.png

切换回第一个请求,我们发现Response中的信息是这样的
图2-4


2-4.png

这就是原始链接 https://www.toutiao.com/search/?keyword=街拍 所返回的内容,只有六十多行代码,执行了一些JavaScript,所以我们最终看到的页面不是由初始页面返回的,而是后来执行的JavaScript向服务器发送了Ajax请求,收到返回的真实数据后才显示出来的。这就是动态页面渲染的流程。
明白了整个流程后,我们要做的最重要的事就是分析返回数据的内容,用python模拟Ajax请求,拿到我们所希望抓取的数据。

def get_page(offset):
    params = {
        'offset': offset,
        'format': 'json',
        'keyword': '街拍',
        'autoload': 'true',
        'count': '20',
        'cur_tab': '1',
        'from': 'search_tab',
    }
    url = 'https://www.toutiao.com/search_content/?'
    try:
        response = requests.get(url, params=params)
        if response.status_code == 200:
            return response.json()
    except requests.ConnectionError:
        return None

下滑几次后,发现只有offset参数变化,所以,构造url,requests获得数据
这里拿到的数据是json格式的

def download_image(jsonData):
    if jsonData.get('data'):
        for item in jsonData.get('data'):
            if item and 'article_url' in item.keys():
                title = item.get('title')
                article_url = item.get('article_url')
                result = get_real_image_path(article_url)
                if result: save_to_mongo(result)
'''

            另外一种数据格式cell,cell type太多,主要分析上面一种
            else:
                #original_page_url
                data = item.get('display')
                #print(display)
                #data = json.loads(display)
                #print(data)
                if data and 'results' in data.keys():
                    results = data.get('results')
                    original_page_urls = [item.get('original_page_url') for item in results]
                # .get('results').get('original_page_url')
                #title = item.get('display').get('title')
                #print(title)
                #print(original_page_urls)'''
'''

取出数据中的data段,发现只有前四张图片的地址可以取到,剩下的图片必须进入文章页才能获得,我们取出文章页的url,requests获得文章页数据

def get_real_image_path(article_url):
    headers = {'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36'}
    response = requests.get(article_url, headers=headers)
    soup = BeautifulSoup(response.text, "lxml")
    title = soup.select('title')[0].get_text()
    image_pattern = re.compile('gallery: JSON.parse\("(.*?)"\),', re.S)
    result = re.search(image_pattern, response.text)

    if result:
        result = result.group(1).replace('\\', '')
        data = json.loads(result)

        if data and 'sub_images' in data.keys():
            sub_images = data.get('sub_images')
            images_urls = [item.get('url') for item in sub_images]
            for image_url in images_urls: download_real_image(image_url)

            return {
                'title': title,
                'url' : article_url,
                'image_urls': images_urls
            }

这里需要加入UA头,否则返回不了数据,拿到数据后,发现图片地址位于

图2-5
2-5.png

这里用正则表达式
gallery: JSON.parse("(.*?)"),
匹配符合条件的,gallery: JSON.parse("")中的数据()这里在正则中表达的是转义字符,有兴趣的可以学习一下正则表达式,这里就不赘述了

我们从sub_images中拿到了所有图片地址,下载过程就很简单了
requests图片地址,获得的response中的content就是图片的数据

def download_real_image(url):
    print('downloading---', url)
    try:
        response = requests.get(url)
        if response.status_code == 200:
            save_image(response.content)
        return None
    except RequestException:
        print('request image fail---', url)
        return None

def save_image(content):
    files_path = '{0}/{1}'.format(os.getcwd(), 'tupian')
    if not os.path.exists(files_path):
        os.mkdir(files_path)

    file_path = '{0}/{1}.{2}'.format(files_path, md5(content).hexdigest(), 'jpg')
    if not os.path.exists(file_path):
        with open(file_path, 'wb') as f:
            f.write(content)

我们还可以把图片的标题和地址写入数据库

def save_to_mongo(result):
    if db[MONGO_TABLE].insert(result):
        print('save success', result)
        return True
    return False

完整代码:jrtt.py

import requests
import re
import json
from hashlib import md5
import os
from bs4 import BeautifulSoup
import pymongo
from config import *
import time



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

def get_page(offset):
    params = {
        'offset': offset,
        'format': 'json',
        'keyword': '街拍',
        'autoload': 'true',
        'count': '20',
        'cur_tab': '1',
        'from': 'search_tab',
    }
    url = 'https://www.toutiao.com/search_content/?'
    try:
        response = requests.get(url, params=params)
        if response.status_code == 200:
            return response.json()
    except requests.ConnectionError:
        return None

def save_to_mongo(result):
    if db[MONGO_TABLE].insert(result):
        print('save success', result)
        return True
    return False

def download_real_image(url):
    print('downloading---', url)
    try:
        response = requests.get(url)
        if response.status_code == 200:
            save_image(response.content)
        return None
    except RequestException:
        print('request image fail---', url)
        return None


def save_image(content):
    files_path = '{0}/{1}'.format(os.getcwd(), 'tupian')
    if not os.path.exists(files_path):
        os.mkdir(files_path)

    file_path = '{0}/{1}.{2}'.format(files_path, md5(content).hexdigest(), 'jpg')
    if not os.path.exists(file_path):
        with open(file_path, 'wb') as f:
            f.write(content)


def get_real_image_path(article_url):
    headers = {'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36'}
    response = requests.get(article_url, headers=headers)
    soup = BeautifulSoup(response.text, "lxml")
    title = soup.select('title')[0].get_text()
    image_pattern = re.compile('gallery: JSON.parse\("(.*?)"\),', re.S)
    result = re.search(image_pattern, response.text)


    if result:
        result = result.group(1).replace('\\', '')
        data = json.loads(result)

        if data and 'sub_images' in data.keys():
            sub_images = data.get('sub_images')
            images_urls = [item.get('url') for item in sub_images]
            for image_url in images_urls: download_real_image(image_url)

            return {
                'title': title,
                'url' : article_url,
                'image_urls': images_urls
            }



def download_image(jsonData):
    if jsonData.get('data'):
        for item in jsonData.get('data'):
            if item and 'article_url' in item.keys():
                title = item.get('title')
                article_url = item.get('article_url')
                result = get_real_image_path(article_url)

                if result: save_to_mongo(result)
            '''
            另外一种数据格式cell,cell type太多,主要分析上面一种
            else:
                #original_page_url
                data = item.get('display')
                #print(display)
                #data = json.loads(display)
                #print(data)
                if data and 'results' in data.keys():
                    results = data.get('results')
                    original_page_urls = [item.get('original_page_url') for item in results]
                # .get('results').get('original_page_url')
                #title = item.get('display').get('title')
                #print(title)
                #print(original_page_urls)'''


def main():

    STARTPAGE = 1
    ENDPAGE = 2

    for i in range(STARTPAGE, ENDPAGE):
        time.sleep(1)
        offset = i * 20
        jsonData = get_page(offset)
        download_image(jsonData)

if __name__ == "__main__":
    main()

config.py

MONGO_URL = 'localhost'

MONGO_DB = 'jiepai'

MONGO_TABLE = 'jiepai'

GROUP_START = 0

GROUP_END = 20

KEYWORD = '街拍'



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

推荐阅读更多精彩内容

  • AJAX 原生js操作ajax 1.创建XMLHttpRequest对象 var xhr = new XMLHtt...
    碧玉含香阅读 3,192评论 0 7
  • 本文详细介绍了 XMLHttpRequest 相关知识,涉及内容: AJAX、XMLHTTP、XMLHttpReq...
    semlinker阅读 13,653评论 2 18
  •   2005 年,Jesse James Garrett 发表了一篇在线文章,题为“Ajax: A new App...
    霜天晓阅读 889评论 0 1
  • Ajax和XMLHttpRequest 我们通常将Ajax等同于XMLHttpRequest,但细究起来它们两个是...
    changxiaonan阅读 2,231评论 0 2
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,647评论 18 139