Ajax爬取今日头条

今日头条爬取街拍图
(动态页面抓取逆向解析实例)

链接:https://www.toutiao.com/

 

目标:爬取今日头条街拍图高清图集

 

目标网站分析:


(索引页分析)
1.进入页面,滑下鼠标,发现新的页面不断加载

容易知道图集索引页的网页是由后台ajax数据通过js渲染成的,由动态页面解析的逆向思路,在Network的选项卡的XHR栏目,发现随着新页面不断加载,新的请求也不断出现,易知网站采取动态网页方式。


2.查看请求的preview的data数据与街拍内容对比,发现正好与我要爬取的内容符合。

 

3.对比我要抓取的内容,title,为标题,article_url为url
(详情页分析)
          1.进去详情页图集网页,F12,在XHR中查看请求的preview,对比data中的title等发现不符合

但是我要提取的是图片的信息,回到All中查看url中照片的序列号对应的请求,也可以在Doc栏中查看
          2.在图片请求中查找具体的图集信息。在preview中滑动鼠标,发现照片的url存储在gallery中

所以不能用BeautifulSoup所以只能用正则来解析。


备注:
1.逆向思路:在F12 + XHR中找json请求查看preview数据并和网页对比(勾选上preserve log 刷新页面),符合就说明找到了ajax数据,title属性的内容就是标题,article_url:是索引页里面下一项的url


2.Network选项卡:
    Elements:查找网页源代码HTML中的任一元素,手动修改任一元素的属性和样式且能实时在浏览器里面得到反馈。
    Console:记录开发者开发过程中的日志信息,且可以作为与JS进行交互的命令行Shell。
    Sources:断点调试JS。
    Network:从发起网页页面请求Request后分析HTTP请求后得到的各个请求资源信息(包括状态、资源类型、大小、所用时间等),可以根据这个进行网络性能优化。

    具体内容参考网站:https://blog.csdn.net/github_38885296/article/details/78847094

 

3.注意页面请求参数:(会改变)
即Query String Parameters
例:
今日头条里街拍综合的数据为
'offset': 0,
'format': 'json',
'keyword': '街拍',
'autoload': 'true',
'count': 20,
'cur_tab': 1,
'from': 'search_tab'
而实际上图集的为:
'offset': 0,
'format': 'json',
'keyword': '街拍',
'autoload': 'true',
'count': 20,
'cur_tab': 3,
'from': 'gallery'


4.urlencode()方法
urlencode可以把字典对象变成url的请求参数
例:
from urllib.parse import urlencode
data = {
    'id':3,
    'keyword':"key",
    'count':20
}
url="https://www.baidu.com/?"+urlencode(data)
print(url)#https://www.baidu.com/?keyword=key&count=20&id=3


5.关于使用正则获取的数据格式问题:
使用replace()方法调整
replace() 方法把字符串中的 old(旧字符串) 替换成 new(新字符串),如果指定第三个参数max,则替换不超过 max 次。
语法:str.replace(old, new[, max])
例:
newresult = result.group(1).replace('\\','')#因为得到的数据中许多地方被插入了\,替换为空格即可得到正确格式


6.json.loads()方法
将已编码的 JSON 字符串解码为 Python 对象
详情可见:http://www.runoob.com/python/python-json.html

7.关于url变换的问题
当我们用爬虫获取的组图的URL并不是真实的地址,今日头条会重新定向
例:
获取的URL:http://toutiao.com/group/6511977315122020871/
实际该组图地址:https://www.toutiao.com/a6511977315122020871/
使用浏览器发现,当我们输入获取的URL时,网页会重新定向到实际地址,但是爬虫程序不能,所以找不到title
解决办法:
给get请求加上headers参数
headers = {'user-agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36'}
response = requests.get(url,headers=headers)
完美解决!!!

参考博主:https://blog.csdn.net/lyxuefeng/article/details/79802846

参照代码:

import json
import re
from hashlib import md5
from urllib.parse import urlencode
import os
import pymongo
import requests
from bs4 import BeautifulSoup
from requests import RequestException
from config2 import *  # 即可以把config里所有的变量引入
from multiprocessing import Pool  # 多进程

# 定义一个MongDB对象
client = pymongo.MongoClient(MONGO_URL)
db = client[MONGO_DB2]


headers = {
    'user-agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36'
    }


# 获取页面索引
def get_page_index(offset, keyword):
    data = {
        'offset': offset,
        'format': 'json',
        'keyword': keyword,
        'autoload': 'true',
        'count': 20,
        'cur_tab': 3,
        'from': 'gallery'
    }
    # 构造ajax请求的url,里面的内容是动态网页的内容
    url = 'https://www.toutiao.com/search_content/?' + urlencode(data)
    # urlencode可以把字典对象变成url的请求参数
    try:
        response = requests.get(url, headers=headers)
        #判断是否请求成功
        if response.status_code == 200:
            return response.text
        return None
    except RequestException:
        print("请求页面索引出错")
        return None


# json解析函数
def parse_page_index(html):
    try:
        # 使用loads()方法把数据转换成json格式的变量(对象)
        data = json.loads(html)
        #判断键名是否存在
        if data and 'data' in data.keys():
            # data.keys()是json格式的data的所有键名
            # 其中有两个判断,即data不为空和json格式的data变量里有data这个键名
            #article_url为详情页链接
            #使用item方法可以提取每个键名
            for item in data.get('data'):
                yield item.get('article_url')
    except ValueError:
        pass


# 详情页请求函数
def get_page_detail(url):
    try:
        response = requests.get(url, headers=headers)
        if response.status_code == 200:
            return response.text
        return None
    except RequestException:
        print("请求详情页出错", url)
        return None


# 获取各个组图的title和属于其的列表的各种值
def parse_page_detail(html, url):
    soup = BeautifulSoup(html, 'lxml')
    title = soup.select('title')[0].get_text()  # 使用css选择器,获得组图名称
    print(title)
    image_pattern = re.compile('gallery: JSON.parse\(\"(.*?)\"\)')  # 该处需注意,请根据实际返回(可能随时间改变)使用正则,括号和引号都要使用转义
    result = re.search(image_pattern, html)  # 查找是否存在image_pattern
    if result:  # 如果result存在
        # print(result.group(1))
        # 格式调整(此处也需注意,可能改变,需根据实际返回调整)
        newresult = result.group(1).replace('\\', '')  # 因为得到的数据中许多地方被插入了\,替换为空格即可得到正确格式
        data = json.loads(newresult)
        # print(newresult)
        #判断键名是否在集合中
        if data and 'sub_images' in data.keys():
            sub_images = data.get('sub_images')  # 获取键名为sub_images的值
            images = [item.get('url') for item in sub_images]  # 以数组形式得到组图中每张图片的url
            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):  # 如果result插入数据库成功,这个输出值得借鉴
        print('存储到MongoDB成功', result)
        return True
    return False


# 下载图片
def download_image(url):
    print('正在下载', url)
    try:
        response = requests.get(url, headers=headers)
        if response.status_code == 200:
            save_image(response.content)  # content属性返回的是二进制格式
        return None
    except RequestException:
        print("请求图片出错", url)
        return None


# 保存图片到文件中
def save_image(content):
    file_path = '{0}/{1}.{2}'.format(os.getcwd() + '\picture', md5(content).hexdigest(), 'jpg')
    #可以先查看保错再根据报错建立picture文件夹
    # 0,1,2分别为路径,文件名,后缀,md5()方法防止保存重复图片
    if not os.path.exists(file_path):  # 如果该文件不存在
        with open(file_path, 'wb') as f:
            f.write(content)
            f.close()


def main(offset):
    html = get_page_index(offset, keyword)
    for url in parse_page_index(html):
        # print(url)
        html = get_page_detail(url)
        # print(html)
        if html:
            result = parse_page_detail(html, url)
            if result:
                save_to_mongo(result)


if __name__ == '__main__':
    # 构造一个列表,传入开始页和结束页
    groups = [x * 20 for x in range(GROUP_START, GROUP_END + 1)]
    # 因为形式比较简单采用多进程的方式
    pool = Pool()
    pool.map(main, groups)

 cofig2:

MONGO_URL = 'localhost'  # url
MONGO_DB2 = 'toutiao2'  # 数据库名称
MONGO_TABLE = 'toutiao'  # 表名

GROUP_START = 0
GROUP_END = 20

keyword = '街拍'

 

 

另一个爬取今日头条索引页图片代码(能实现):

# coding=utf-8
import json
import os
import re
import urllib
from urllib import request

'''
Python3.X 动态页面爬取
方法一:逆向解析实例
爬取今日头条关键词搜索结果的所有详细页面大图片并按照关键词及文章标题分类存储图片
图片不是高清图是索引页的图片
'''

class CrawlOptAnalysis(object):
    #设定全局变量,初始化操作
    def __init__(self, search_word="美女"):
        self.search_word = search_word
        self.headers = {
            'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.100 Safari/537.36',
            'X-Requested-With': 'XMLHttpRequest',
            'Host': 'www.toutiao.com',
            'Referer': 'http://www.toutiao.com/search/?keyword={0}'.format(urllib.parse.quote(self.search_word)),
            'Accept': 'application/json, text/javascript',
        }

    def _crawl_data(self, offset):
        '''
        模拟依据传入 offset 进行分段式上拉加载更多 item 数据爬取
        '''
        #这里format的第二个参数self.search_word中文必须编码成字符才能添加到url中
        #这里的url来源:打开F12找到XHR选项卡
        url = 'http://www.toutiao.com/search_content/?offset={0}&format=json&keyword={1}&autoload=true&count=20&cur_tab=1'.format(offset, urllib.parse.quote(self.search_word))
        print(url)
        try:
            with request.urlopen(url, timeout=10) as response:
                content = response.read()
        #as e的方式可以借鉴
        except Exception as e:
            content = None
            print('crawl data exception.'+str(e))
        return content

    def _parse_data(self, content):
        '''
        解析每次上拉加载更多爬取的 item 数据及每个 item 点进去详情页所有大图下载链接
        [
            {'article_title':XXX, 'article_image_detail':['url1', 'url2', 'url3']},
            {'article_title':XXX, 'article_image_detail':['url1', 'url2', 'url3']}
        ]
        '''
        #单独判断content是否存在不放在try,except中
        if content is None:
            return None
        try:
            data_list = json.loads(content)['data']#把抓取到的json数据转成dict并获取键名
            print(data_list)
            result_list = list()#初始化结果列表
            for item in data_list:
                #结果键值对,item[]可以提取键值,这里的item用法
                result_dict = {'article_title': item['title']}
                #print(type(result_dict))
                url_list = list()
                #用list的append属性
                for url in item['image_detail']:
                    url_list.append(url['url'])
                result_dict['article_image_detail'] = url_list
                result_list.append(result_dict)
        except Exception as e:
            print('parse data exception.'+str(e))
        return result_list

    def _save_picture(self, page_title, url):
        '''
        把爬取的所有大图下载下来
        下载目录为./output/search_word/page_title/image_file
        '''
        if url is None or page_title is None:
            print('save picture params is None!')
            return
        #这里的reg_str值得注意
        reg_str = r"[\/\\\:\*\?\"\<\>\|]"  #For Windows File filter: '/\:*?"<>|'
        page_title = re.sub(reg_str, "", page_title)
        save_dir = './output/{0}/{1}'.format(self.search_word, page_title)
        #文件保存
        if os.path.exists(save_dir) is False:
            os.makedirs(save_dir)
        save_file = save_dir + url.split("/")[-1] + '.png'
        if os.path.exists(save_file):
            return
        try:
            #打开网页的同时打开要存储到的文件夹
            with request.urlopen(url, timeout=30) as response, open(save_file, 'wb') as f_save:
                f_save.write(response.read())
            print('Image is saved! search_word={0}, page_title={1}, save_file={2}'.format(self.search_word, page_title, save_file))
        except Exception as e:
            print('save picture exception.'+str(e))

    #主函数
    def go(self):
        offset = 0
        while True:
            page_list = self._parse_data(self._crawl_data(offset))
            if page_list is None or len(page_list) <= 0:
                break
            try:
                for page in page_list:
                    #print(type(page))
                    article_title = page['article_title']
                    for img in page['article_image_detail']:
                        self._save_picture(article_title, img)
            except Exception as e:
                print('go exception.'+str(e))
            finally:
                offset += 20


if __name__ == '__main__':
    CrawlOptAnalysis("美女").go()
    CrawlOptAnalysis("旅游").go()
    CrawlOptAnalysis("风景").go()

 

 

你可能感兴趣的:(爬虫,Python,今日头条,爬虫)