您的位置:首页 > 编程语言 > Python开发

Python爬虫实战02:分析Ajax请求并抓取今日头条街拍

2018-03-29 21:52 1151 查看

1 目标网站分析

首先我们打开今日头条网站,搜索 街拍,点击图集,这里每就是我们要爬取的目录,我们称为索引页。1



点开一个标题,进去,称为详情页。2这里面的图是我们所要爬取的。比如这里可以点击图片,共7张图。2



这里我们可以想到,可以先把详情页的每个标题URL爬取下来,再请求详情页得到每个图集。

分析详情页代码,打开谷歌浏览器开发工具,选择Network,刷新网页。发现第一个请求中返回的不含图片的任何信息。

想到头条应该是Ajax请求,选择XHR,继续刷新,点击图集。发现我们点击图集发出的请求返回的代码中
data
中含有我们想要的图片信息,是Json格式。可以看到title信息,刚好符合。

3


Headers,属于get请求,可以看到请求参数。

4


也可以看到我们请求索引页的网址。



然后滑动网页,发现左边又多了个请求,offset=20,由此得知索引页可通过改变offset来换页。6



继续看详情页,我们想找到每个图集的内容。同样打开network工具,刷新网页。选择XHR,发现每个请求中都不包含图集信息。选择ALL查看,我们可以看到对应网址的请求返回了一段代码。我们右击图片,新窗口查看,得到图片的地址后面的数字。到前面的代码中搜索,发现图片信息恰好在gallery: JSON.parse(” “)中,是Json格式。其中’sub_image’的值包含的就是我们想要获取的图集的信息。7



理一下基本思路:

爬取索引页的信息。通过分析ajax请求,得到每个详情页的网址。(比如这里就返回了18个详情页)

爬取详情页的信息,分析网页代码,用正则表达式得到每个图集的信息。

将爬取的信息存到MongoDB,并下载图片。

通过改变offset爬取多页信息,利用多线程加快速度。

2 流程框架

抓取索引页内容

利用requests请求目标站点,得到索引网页HTML代码,返回结果。

抓取详情页内容

解析返回结果,得到详情页的链接,并进一步抓取详情页的信息。

下载图片与保存数据库

将下载的图片保存到本地,并把页面信息及图片URL保存至MongDB.

开启循环和多线程

对多页内容遍历,开启多线程提高抓取速度。

3 爬虫实战

使用pycharm。

3.1 抓取索引页内容

def get_page_index(offset, keyword):
"""抓取索引页的内容"""
data = {
c83a
# 请求参数,offset和keyword我们设置成变量,方便改变。
'offset': offset,
'format': 'json',
'keyword': keyword,
'autoload': 'true',
'count': 20,
'cur_tab': 3,
'from': 'gallery'
}
# urlencode()可以把字典对象转化为url的请求参数
url = 'https://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


3.2 解析索引数据

def parse_page_index(html):
""" 解析索引数据"""
# json.loads()对JSON数据进行解码,转换成一个字典
data = json.loads(html)
# 当data这个字典存在且'data'键名存在与data字典中。data.keys()返回data这个字典所有的键名
if data and 'data' in data.keys():
# get() 函数返回字典中指定键的值,在这里遍历data字典中键名为'data'的
# 值,每个元素分别为一个图集。
for item in data.get('data'):
# 对于'data'的值中的每个元素,建立一个生成器,得到每个网址
yield item.get('article_url')  # 'article_url'中信息是每个图集的网址


3.3 获取详情页信息

def get_page_detail(url):
""" 拿到详情页图的信息"""
try:
# 此处不加headers会导致访问详情页失败
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64;x64)AppleWebKit/537.36(KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36'}
response = requests.get(url, headers=headers)
if response.status_code == 200:
return response.text
return None
except RequestException:
print('请求详情页出错', url)
return None


3.4 解析详情页

def parse_page_detail(html, url):
""" 解析详情页"""
# 声明解析后的网页对象
soup = BeautifulSoup(html, 'lxml')
# 通过传入css选择器,选择第一个<title>标签,获取文本内容,就是图集的标题。
title = soup.select('title')[0].get_text()
# 声明一个正则表达式对象,来匹配我们想要的Json语句。注意re.S使 . 能匹配任意字符。
images_pattern = re.compile('gallery: JSON.parse\("(.*?)"\),', re.S)
result = re.search(images_pattern, html)
# 注意:这里的Json语句包含转义字符 \ ,不去掉会报错
result = result.group(1).replace('\\', '')
# result = re.sub(r'\\', '', result.group(1))
if result:  # 结果存在则进行
data = json.loads(result)  # 把Json转换为字典
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]  # 构造一个图集列表,包含每个图片的地址。
for image in images:
download_image(image)  # 下载每张图片
return {  # 返回一个字典,格式化数据,准备存入MongoDB
'title': title,
'url': url,
'images': images,
}


3.5 定义下载图片函数

def download_image(url):  # 传入的是每张图片的地址
""" 下载图片"""
print('正在下载', url)  # 调试信息
try:
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
'(KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36'}
response = requests.get(url, headers=headers)
if response.status_code == 200:
save_image(response.content)  # 保存图片,content返回二进制内容(当保存图片视频时)
return None
except RequestException:
print('请求图片出错', url)
return None


3.6 定义保存图片函数

def save_image(content):
"""存图片"""
# 定义文件路径,文件名把图片信息md5加密,保证每个文件名不同。
file_path = '{0}/{1}.{2}'.format(os.getcwd() +
'\images', md5(content).hexdigest(), 'jpg')
if not os.path.exists(file_path):  # 如果文件不存在
with open(file_path, 'wb') as f:
f.write(content)
'''
w     以写方式打开,不存在则创建
a     以追加模式打开 (从 EOF 开始, 必要时创建新文件)
r+     以读写模式打开
w+     以读写模式打开 (参见 w )
a+     以读写模式打开 (参见 a )
rb     以二进制读模式打开
wb     以二进制写模式打开 (参见 w )
ab     以二进制追加模式打开 (参见 a )
rb+    以二进制读写模式打开 (参见 r+ )
wb+    以二进制读写模式打开 (参见 w+ )
ab+    以二进制读写模式打开 (参见 a+ )'''


3.7 存储到数据库

client = pymongo.MongoClient(MONGO_URL)
db = client[MONGO_DB]
# 其中MONGO_DB,MONGO_URL为配置文件中的参数
def save_to_mongo(result):
"""存储文件到数据库"""
if db[MONGO_DB].insert(result):
print('存储成功', result)
return True
return False


3.8 主函数

def main(offset):
""" 主函数"""
html = get_page_index(offset, KEYWORD)  # 获取索引页网页内容
for url in parse_page_index(html):  # parse_page_index()返回一个生成器,生成每个图集的地址
html = get_page_detail(url)  # 得到每个图集详情页的内容
if html:  # 如果内容返回成功
result = parse_page_detail(html, url) # 解析详情页,返回一个字典结果
save_to_mongo(result)  # 存入数据库


3.9 运行

if __name__ == '__main__':
groups = [x * 20 for x in range(GROUP_START, GROUP_END + 1)]  # 生成一个offset列表
pool = Pool()  # 声明一个进程池
pool.map(main, groups)
pool.close()
pool.join()


3.10 头文件

import json
import os
import re
from hashlib import md5
from multiprocessing import Pool

from urllib.parse import urlencode
import pymongo
import requests
from bs4 import BeautifulSoup
from requests.exceptions import RequestException

from ToutiaoJiepai.config import *


3.11 创建配置文件config.py

# config.py
MONGO_URL = 'localhost'
MONGO_DB = 'toutiao'
MONGO_TABLE = 'toutiao'

GROUP_START = 1
GROUP_END = 1
KEYWORD = '街拍'


多用
if
语句的异常语句来保证程序的顺利进行。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: