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

【目录】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

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容