美文网首页
【练习】分析Ajax请求并抓取今日头条街拍美图

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

作者: 阿杨_你的花 | 来源:发表于2019-04-02 21:58 被阅读0次

    【目录】2019-04-02

    image.png

    第一部分:前言

    一、所需工具(4个)

    jupyter notebook(单进程用)、mongodb(存储数据用)、studio-3t(查看数据用)、sublime(多进程用)

    二、主要知识点(4个)

    urlib库、requests库、BeautifulSoup库、正则表达式(很少)

    三、操作流程(3个阶段)
    • 1、单个模块运行:单个方法模块调试通畅,保证子程序没有问题
    • 2、单进程运行:参数写死,请求一个图集的结果
    • 3、多进程运行:参数可变,请求多个图集的结果

    第二部分:正文

    一、分析目标网站(4个关键问题)

    1、确认数据是怎么加载的--找到ajax文件


    image.png

    2、找到各个图集的区别--发现offset变化规律
    下拉加载,依次点击XHR文件查看,看到offset变化规律:0,20,40.....


    image.png

    3、定位详情页链接--在哪里?
    各种尝试后,发现详情页链接在article_url这个参数中


    image.png

    4、定位图片链接--在哪里?
    preview的源代码中可以发现,BASE_DATA.galleryInfo里面gallery这个参数中就含有url链接。测试一下,copy其中的一个url:消除“\\”后访问,发现就是图集中图片。
    gallery中copy一个url:http://pb3.pstatp.com/origin/dfic-imagehandler/7e8da4a8-dad7-4951-bf81-78988fae40bb

    image.png
    二、设定思路步骤(9个步骤)

    1、载入必要的包

    # 1.导入必要的库:
    import requests  #请求网页用
    from requests.exceptions import RequestException  #做requests的异常处理
    from urllib.parse import urlencode  #url编码用
    from bs4 import BeautifulSoup  #网页解析用
    import urllib  #请求网页用
    import json  #格式转换用
    import re  #正则表达式用
    import pymongo  #连接数据库用
    import os #路径相关的函数
    from hashlib import md5  #图片命名用
    from multiprocessing import Pool  #建立多进程用
    

    2、请求索引页

    # 1.导入必要的库:
    import requests  #请求网页用
    from requests.exceptions import RequestException  #做requests的异常处理
    from urllib.parse import urlencode  #url编码用
    from bs4 import BeautifulSoup  #网页解析用
    import urllib  #请求网页用
    import json  #格式转换用
    import re  #正则表达式用
    import pymongo  #连接数据库用
    import os #路径相关的函数
    from hashlib import md5  #图片命名用
    from multiprocessing import Pool  #建立多进程用
    
    keyword='街拍图片'
    offset=0
    
    #放在全局中,不要放在方法中,不然每次请求要重复写headers参数。
    headers={"User-Agent":"Mozilla/5.0 (Windows NT 10.0; WOW64)","Referer":"https://www.toutiao.com/"}
    
    # 2.获取索引页并分析:
    def get_page_index(offset, keyword):
        data = {
            'offset': offset,
            'format': 'json',
            'keyword':  keyword,
            'autoload': 'true',
            'count': 20,
            'cur_tab': 1,   # cur_tab为3指的是图集板块,数过来第三个,若为1则指代综合板块
            'from': 'search_tab'
        }
        url = 'https://www.toutiao.com/api/search/content/?' + urlencode(data)  
        try:
            response = requests.get(url,headers=headers)
            if response.status_code == 200:
                return response.text
            else:
                return None
        except RequestException:
            print('请求索引页错误')
            return None
    
    #8、定义一个主函数,调用之前的方法
    def main(offset):
        html=get_page_index(offset,keyword)
        print(html)
     
    # 9、只运行本文件中的主函数
    if __name__=='__main__':
        main(offset)
    

    预期结果:返回结果跟索引页的preview中一致


    image.png

    3、解析索引页,得到详情页url

    # 1.导入必要的库:
    import requests  #请求网页用
    from requests.exceptions import RequestException  #做requests的异常处理
    from urllib.parse import urlencode  #url编码用
    from bs4 import BeautifulSoup  #网页解析用
    import urllib  #请求网页用
    import json  #格式转换用
    import re  #正则表达式用
    import pymongo  #连接数据库用
    import os #路径相关的函数
    from hashlib import md5  #图片命名用
    from multiprocessing import Pool  #建立多进程用
    
    keyword='街拍图片'
    offset=20
    
    #放在全局中,不要放在方法中,不然每次请求要重复写headers参数。
    headers={"User-Agent":"Mozilla/5.0 (Windows NT 10.0; WOW64)","Referer":"https://www.toutiao.com/"}
    
    # 2.获取索引页并分析:
    def get_page_index(offset, keyword):
        data = {
            'offset': offset,
            'format': 'json',
            'keyword':  keyword,
            'autoload': 'true',
            'count': 20,
            'cur_tab': 1,   # cur_tab为3指的是图集板块,数过来第三个,若为1则指代综合板块
            'from': 'search_tab'
        }
        url = 'https://www.toutiao.com/api/search/content/?' + urlencode(data)  #url写错,卡了3天啊
        try:
            response = requests.get(url,headers=headers)
            if response.status_code == 200:
                return response.text
            else:
                return None
        except RequestException:
            print('请求索引页错误')
            return None
    
    # 3、解析索引页内容
    # 分析ajax请求的返回结果,拿到详情页的url
    def parse_page_index(html):
        data = json.loads(html) # 加载返回的json数据
        if data and 'data' in data.keys():  #确保返回的信息中含有data这个信息
            for item in data.get('data'):
                yield item.get('article_url') #构造一个生成器
    
    #8、定义一个主函数,调用之前的方法
    def main(offset):
        html=get_page_index(offset,keyword)
        for url in parse_page_index(html):  #返回的是一个迭代器,每次输出一个网址
            print(url)
     
    # 9、只运行本文件中的主函数
    if __name__=='__main__':
        main(offset)
    

    预期结果:详情页的url


    image.png

    4、请求详情页
    5、解析详情页,得到目标图片的url

    # 1.导入必要的库:
    import requests  #请求网页用
    from requests.exceptions import RequestException  #做requests的异常处理
    from urllib.parse import urlencode  #url编码用
    from bs4 import BeautifulSoup  #网页解析用
    import urllib  #请求网页用
    import json  #格式转换用
    import re  #正则表达式用
    import pymongo  #连接数据库用
    import os #路径相关的函数
    from hashlib import md5  #图片命名用
    from multiprocessing import Pool  #建立多进程用
    
    keyword='街拍图片'
    offset=20
    
    #放在全局中,不要放在方法中,不然每次请求要重复写headers参数。
    headers={"User-Agent":"Mozilla/5.0 (Windows NT 10.0; WOW64)","Referer":"https://www.toutiao.com/"}
    
    # 2.获取索引页并分析:
    def get_page_index(offset, keyword):
        data = {
            'offset': offset,
            'format': 'json',
            'keyword':  keyword,
            'autoload': 'true',
            'count': 20,
            'cur_tab': 1,   # cur_tab为3指的是图集板块,数过来第三个,若为1则指代综合板块
            'from': 'search_tab'
        }
        url = 'https://www.toutiao.com/api/search/content/?' + urlencode(data)  #url写错,卡了3天啊
        try:
            response = requests.get(url,headers=headers)
            if response.status_code == 200:
                return response.text
            else:
                return None
        except RequestException:
            print('请求索引页错误')
            return None
    
    # 3、解析索引页内容
    # 分析ajax请求的返回结果,拿到详情页的url
    def parse_page_index(html):
        data = json.loads(html) # 加载返回的json数据
        if data and 'data' in data.keys():  #确保返回的信息中含有data这个信息
            for item in data.get('data'):
                yield item.get('article_url') #构造一个生成器
    
    # 4、请求详情页的内容
    def get_page_detail(url):
        try:
            response = requests.get(url,headers=headers)
            if response.status_code == 200:
                return response.text
            else:
                return None
        except RequestException:
            print('详情页页错误', url)
            return None
    
    # 5、解析详情页的内容
    def parse_page_detail(html,url):
        try:
            soup = BeautifulSoup(html, 'lxml')  #解析详情页的内容
            title = soup.select('title')[0].get_text()  #获取文章title
            images_pattern = re.compile('gallery: JSON.parse\("(.*?)"\)',re.S) #确定匹配模式
            result = re.search(images_pattern,html) #匹配内容
            str=re.sub(r'(\\)','',result.group(1)) #去掉url链接中防作弊的多余的双斜线“\\”
            if str:                         #如果匹配到内容,执行接下来的操作
                data = json.loads(str)
                if data and 'sub_images' in data.keys():  #确保返回的信息中含有sub_images这个信息
                    sub_images=data.get('sub_images')
                    images=[item.get('url') for item in sub_images]  #提取sub_images中图片的url链接
                    return{
                        'title':title,  #详情页标题
                        'url':url,      #详情页链接
                        'images':images #图片链接
                    }
        except:
            return None #跳过异常继续执行
    
    #8、定义一个主函数,调用之前的方法
    def main(offset):
        html=get_page_index(offset,keyword)
        for url in parse_page_index(html):  #返回的是一个迭代器,每次输出一个网址
            html = get_page_detail(url)
            if html:
                result=parse_page_detail(html,url) #传入详情页链接、详情页内容,进行解析
                print(result)
     
    # 9、只运行本文件中的主函数
    if __name__=='__main__':
        main(offset)
    

    预期结果:详情页标题、详情页链接、图片链接


    image.png

    6、下载图片 to 本地文件夹
    7、返回数据 to mongodb

    # 1.导入必要的库:
    import requests  #请求网页用
    from requests.exceptions import RequestException  #做requests的异常处理
    from urllib.parse import urlencode  #url编码用
    from bs4 import BeautifulSoup  #网页解析用
    import urllib  #请求网页用
    import json  #格式转换用
    import re  #正则表达式用
    import pymongo  #连接数据库用
    from hashlib import md5  #图片命名用
    import os
    from multiprocessing import Pool  #建立多进程用
    
    keyword='街拍图片'
    offset=100
    
    MONGO_URL='localhost'
    MONGO_DB='toutiao'
    MONGO_TABLE='toutiao'
    
    client = pymongo.MongoClient(MONGO_URL)  # 连接MongoDB
    db = client[MONGO_DB]  # 如果已经存在连接,否则创建数据库
    
    #放在全局中,不要放在方法中,不然每次请求要重复写headers参数。
    headers={"User-Agent":"Mozilla/5.0 (Windows NT 10.0; WOW64)","Referer":"https://www.toutiao.com/"}
    
    # 2.获取索引页并分析:
    def get_page_index(offset, keyword):
        data = {
            'offset': offset,
            'format': 'json',
            'keyword':  keyword,
            'autoload': 'true',
            'count': 20,
            'cur_tab': 1,   # cur_tab为3指的是图集板块,数过来第三个,若为1则指代综合板块
            'from': 'search_tab'
        }
        url = 'https://www.toutiao.com/api/search/content/?' + urlencode(data)  #url写错,卡了3天啊
        try:
            response = requests.get(url,headers=headers)
            if response.status_code == 200:
                return response.text
            else:
                return None
        except RequestException:
            print('请求索引页错误')
            return None
    
    # 3、解析索引页内容
    # 分析ajax请求的返回结果,拿到详情页的url
    def parse_page_index(html):
        data = json.loads(html) # 加载返回的json数据
        if data and 'data' in data.keys():  #确保返回的信息中含有data这个信息
            for item in data.get('data'):
                yield item.get('article_url') #构造一个生成器
    
    # 4、请求详情页的内容
    def get_page_detail(url):
        try:
            response = requests.get(url,headers=headers)
            if response.status_code == 200:
                return response.text
            else:
                return None
        except RequestException:
            print('详情页页错误', url)
            return None
    
    # 5、解析详情页的内容
    def parse_page_detail(html,url):
        try:
            soup = BeautifulSoup(html, 'lxml')  #解析详情页的内容
            title = soup.select('title')[0].get_text()  #获取文章title
            images_pattern = re.compile('gallery: JSON.parse\("(.*?)"\)',re.S) #确定匹配模式
            result = re.search(images_pattern,html) #匹配内容
            str=re.sub(r'(\\)','',result.group(1)) #去掉url链接中防作弊的多余的双斜线“\\”
            if str:                         #如果匹配到内容,执行接下来的操作
                data = json.loads(str)
                if data and 'sub_images' in data.keys():  #确保返回的信息中含有sub_images这个信息
                    sub_images=data.get('sub_images')
                    images=[item.get('url') for item in sub_images]  #提取sub_images中图片的url链接
                    for image in images:
                        download_img(image)   #调用一个方法,下载图片
                    return{
                        'title':title,  #详情页标题
                        'url':url,      #详情页链接
                        'images':images #图片链接
                    }
        except:
            return None #跳过异常继续执行
    
    # 6、存储到mongodb
    def save_to_mongo(result):
        if result:
            if db[MONGO_TABLE].insert(result):  # 插入数据
                print('存储成功', result)
                return True
        return False
    
    # 7、下载图片(进度+存图)
    def download_img(url):
        print('正在下载', url)
        try:
            response = requests.get(url, headers=headers)
            if response.status_code == 200:
                save_img(response.content)  #content返回二进制内容,图片一般返回content
            else:
                return None
        except RequestException:
            print('下载图片错误', url)
            return None
    
    #存储图片方法
    def save_img(content):
        #生成目标文件路径,图片用md5命名,保证不重复
        file_path = '{0}/{1}.{2}'.format(r'C:\Users\luna\jiepai3', md5(content).hexdigest(), 'jpg')
        if not os.path.exists(file_path):
            with open(file_path, 'wb')as f:
                f.write(content)
                f.close()
    
    #8、定义一个主函数,调用之前的方法
    def main(offset):
        html=get_page_index(offset,keyword)
        for url in parse_page_index(html):  #返回的是一个迭代器,每次输出一个网址
            html = get_page_detail(url)
            if html:
                result=parse_page_detail(html,url) #传入详情页链接、详情页内容,进行解析,下载图片
                save_to_mongo(result) #把返回内容存入数据库
    
    # 9、只运行本文件中的主函数
    if __name__=='__main__':
        main(offset)
    

    预期结果:
    (1)运行结果


    image.png

    (2)本地文件夹载入图片


    image.png

    (3)mongodb中载入数据


    image.png

    8、调用主函数
    9、启动多进程

    # 1.导入必要的库:
    import requests  #请求网页用
    from requests.exceptions import RequestException  #做requests的异常处理
    from urllib.parse import urlencode  #url编码用
    from bs4 import BeautifulSoup  #网页解析用
    import urllib  #请求网页用
    import json  #格式转换用
    import re  #正则表达式用
    import pymongo  #连接数据库用
    import os #路径相关的函数
    from hashlib import md5  #图片命名用
    from multiprocessing import Pool  #建立多进程用
    
    GROUP_START=1
    GROUP_END=20
    keyword='街拍图片'
    
    MONGO_URL='localhost'
    MONGO_DB='toutiao'
    MONGO_TABLE='toutiao'
    
    client = pymongo.MongoClient(MONGO_URL)  # 连接MongoDB
    db = client[MONGO_DB]  # 如果已经存在连接,否则创建数据库
    
    #放在全局中,不要放在方法中,不然每次请求要重复写headers参数。
    headers={"User-Agent":"Mozilla/5.0 (Windows NT 10.0; WOW64)","Referer":"https://www.toutiao.com/"}
    
    # 2.获取索引页并分析:
    def get_page_index(offset, keyword):
        data = {
            'offset': offset,
            'format': 'json',
            'keyword':  keyword,
            'autoload': 'true',
            'count': 20,
            'cur_tab': 1,   # cur_tab为3指的是图集板块,数过来第三个,若为1则指代综合板块
            'from': 'search_tab'
        }
        url = 'https://www.toutiao.com/api/search/content/?' + urlencode(data)  #url写错,卡了3天啊
        try:
            response = requests.get(url,headers=headers)
            if response.status_code == 200:
                return response.text
            else:
                return None
        except RequestException:
            print('请求索引页错误')
            return None
    
    # 3、解析索引页内容
    # 分析ajax请求的返回结果,拿到详情页的url
    def parse_page_index(html):
        data = json.loads(html) # 加载返回的json数据
        if data and 'data' in data.keys():  #确保返回的信息中含有data这个信息
            for item in data.get('data'):
                yield item.get('article_url') #构造一个生成器
    
    # 4、请求详情页的内容
    def get_page_detail(url):
        try:
            response = requests.get(url,headers=headers)
            if response.status_code == 200:
                return response.text
            else:
                return None
        except RequestException:
            print('详情页页错误', url)
            return None
    
    # 5、解析详情页的内容
    def parse_page_detail(html,url):
        try:
            soup = BeautifulSoup(html, 'lxml')  #解析详情页的内容
            title = soup.select('title')[0].get_text()  #获取文章title
            images_pattern = re.compile('gallery: JSON.parse\("(.*?)"\)',re.S) #确定匹配模式
            result = re.search(images_pattern,html) #匹配内容
            str=re.sub(r'(\\)','',result.group(1)) #去掉url链接中防作弊的多余的双斜线“\\”
            if str:                         #如果匹配到内容,执行接下来的操作
                data = json.loads(str)
                if data and 'sub_images' in data.keys():  #确保返回的信息中含有sub_images这个信息
                    sub_images=data.get('sub_images')
                    images=[item.get('url') for item in sub_images]  #提取sub_images中图片的url链接
                    for image in images:
                        download_img(image)   #调用一个方法,下载图片
                    return{
                        'title':title,  #详情页标题
                        'url':url,      #详情页链接
                        'images':images #图片链接
                    }
        except:
            return None #跳过异常继续执行
    
    # 6、存储到mongodb
    def save_to_mongo(result):
        if result:
            if db[MONGO_TABLE].insert(result):  # 插入数据
                print('存储成功', result)
                return True
        return False
    
    # 7、下载图片(进度+存图)
    def download_img(url):
        print('正在下载', url)
        try:
            response = requests.get(url, headers=headers)
            if response.status_code == 200:
                save_img(response.content)  #content返回二进制内容,图片一般返回content
            else:
                return None
        except RequestException:
            print('下载图片错误', url)
            return None
    
    #存储图片方法
    def save_img(content):
        #生成目标文件路径,图片用md5命名,保证不重复
        file_path = '{0}/{1}.{2}'.format(r'C:\Users\luna\jiepai3', md5(content).hexdigest(), 'jpg')
        if not os.path.exists(file_path):
            with open(file_path, 'wb')as f:
                f.write(content)
                f.close()
    
    #8、定义一个主函数,调用之前的方法
    def main(offset):
        html=get_page_index(offset,keyword)
        for url in parse_page_index(html):  #返回的是一个迭代器,每次输出一个网址
            html = get_page_detail(url)
            if html:
                result=parse_page_detail(html,url) #传入详情页链接、详情页内容,进行解析,下载图片
                save_to_mongo(result) #把返回内容存入数据库
     
    # 9、只运行本文件中的主函数
    if __name__=='__main__':
        groups = [i * 20 for i in list(range(GROUP_START,GROUP_END))]  # python3 range()不能直接生成列表,需要list一下
        pool = Pool() #创建进程池
        pool.map(main,groups)  # 第一个参数是函数,第二个参数是一个迭代器,将迭代器中的数字作为参数依次传入函数中
    

    预期结果:
    (1)图片大量存入文件夹
    (2)mongodb中数据条数变多

    第三部分:后记

    一、耗时:2天
    二、感受(6点)

    1、0编程基础学习,各个节点都会卡你一下,主要是不熟悉各种报错。

    2、中间有一些支线任务:比如说安装工具,会耽搁一些时间。

    3、明确知道自己有拖延症的人,没必要开头就一股脑安装一堆工具,到了要用的时候再安装吧,不然很难起头开始做。mongodb、stduio-3t都是到了步骤5我才安装的

    4、实在找不到问题,找会的人问一下。别人点拨一下,会更容易找到错误点。我应该是qq群里问过一个问题,其它都是百度+自己调试解决。

    5、新人小白的方法:
    (1)化整为零,代码一段一段跑通,这样容易推进一些。
    (2)明确自己的预期结果。代码运行后,如果返回结果跟预期结果一致,那就通过;如果不一致,那就继续找问题。

    6、这一天太能坐了,竟然坐了9个小时。搞完后,胸闷气短了,赶紧走走,坑爹啊,以后不能这样了!

    三、总结问题,踩的坑(10个)
    1、chrome中F12后,preview不显示源代码,显示的是网页或者图片。

    这个是谷歌浏览器的bug,换个浏览器就好了。

    2、找不到offset

    筛选XHR文件,在headers栏目,Query String Parameters模块,就可以看到offset的变化。

    3、xhr是什么文件类型?

    xhr:XMLHttpRequest在后台与服务器交换数据,这意味着可以在不加载整个网页的情况下,对网页某部分的内容进行更新。它是Ajax的一种用法,而Ajax并不是一门语言,只是一种不需要加载整个网页,只是更新局部内容的技术。

    4、json书写格式什么样?

    对象:{"name":"Michael","age":24}
    数组:比如:[{"name":"Michael","age":24},{"name":"Tom","age":25}]
    值:{"name":"Michael", "birthday":{"month":8,"day":26}},类似于对象嵌套对象.

    5、query string parameters 这是什么啊?

    Query String Parameters指的就是通过在URL中携带的方式提交的参数,也就是get请求中需要的参数

    6、如何简单的理解Python中的if name == 'main' 这个用法

    当前理解:只运行本文件中主函数

    7、多进程作用

    在爬虫中主要目的是提高爬取的效率,实现秒爬

    8、多进程创建和使用

    在Windows上要想使用进程模块,就必须把有关进程的代码写在当前.py文件的

    if __name__=='__main__':
        pool = Pool() #创建进程池
    

    语句的下面,才能正常使用Windows下的进程模块。Unix/Linux下则不需要。

    9、多进程在notebook中卡死的问题。

    解决办法:换在编辑器中运行,例如,我就是在sublime text运行python

    10、mongodb的安装和启动

    (1)下载一个mongodb安装包进行安装
    (2)配置系统变量


    image.png

    (3)用cmd启动:

    • 第一步,用cd转入mongodb的路径下:cd E:\装机软件包\mongodb\bin
    • 第二步,启动:mongod --dbpath "E:\装机软件包\mongodb\data\db"


      image.png

      路径是指mongodb的安装路径,通过以上3步就可以启动了。

    As you can see!第一个项目总是断断续续,坎坎坷坷的,不过终究还是可以解决的,继续加油吧!——2019.04.02

    相关文章

      网友评论

          本文标题:【练习】分析Ajax请求并抓取今日头条街拍美图

          本文链接:https://www.haomeiwen.com/subject/bsanbqtx.html