声明:此篇文章主要是观看静觅教学视频后做的笔记,原教程地址https://cuiqingcai.com/
实现流程介绍
1.抓取索引页内容:利用requests请求目标站点,得到索引网页HTML代码,返回结果
2.抓取详情页内容:解析返回结果,得到详情页的链接,并进一步抓取详情页信息
3.下载图片与保存数据库:将图片下载到本地,并把页面信息及图片URL保存至MongoDB
4.开启循环及多线程:对多页内容遍历,开启多线程提高抓取速度
具体实现
1. 首先访问今日头条网站输入关键字来到索引页,我们需要通过分析网站来拿到进入详细页的url
经过观察可以发现每次滑动鼠标滚轮,新的标题链接就会被显示,所以可以发现其后台为Ajax请求,通过浏览器Network选项卡的XHR可以找到Ajax的链接,其为一个json数据,以搜索词街拍为例,其链接地址如下:
https://www.toutiao.com/search_content/?offset=0&format=json&keyword=%E8%A1%97%E6%8B%8D&autoload=true&count=20&cur_tab=1&from=search_tab
2.通过点击查看Query String Parameters中的内容,可以看到一些类似字典的数据,所以这是一会需要通过urlencode来转码拼接成最终访问的地址
offset: 0
format: json
keyword: 街拍
autoload: true
count: 20
cur_tab: 1
from: search_tab
3.随着向下滑动滚动条显示更多的图片索引,会发现刷出了很多新的ajax请求,通过这个我们可以知道我们之后可以通过改变offset参数来获取不同的拿到不同的索引界面,从而获得不同的图集详细页url。开始只需实现一个offset参数的爬取,最后通过进程池Pool来创建实现多进程爬取不同offset参数的URL,加快爬取速度
4.接下来就是分析查找图集详细页的代码,来找到图片的url,这个图片url隐藏的比较深,都在JS代码中,所以不能使用BeautifulSoup和PyQuery来解析了,只能通过正则解析,使用正则解析要注意匹配规则一定要写对。刷新页面后,自己基础比较差,找了好久换了火狐浏览器,又换回谷歌,最后在Network选项卡的Doc发现下面这个链接,而图片地址就藏在gallery: JSON.parse里
https://www.toutiao.com/a6585311263927042573/
5.代码实现
代码直接进行展示吧,需要的注释我已经写在代码里了,先编辑一个config.py的文件,里面设置了代码中用到的变量
MONGO_URL = 'localhost'
MONGO_DB = 'toutiao'
MONGO_TABLE = 'toutiao'
GROUP_START = 1
GROUP_END = 20
KEYWORD='街拍'
#!/usr/bin/env python
# coding=utf-8
from urllib.parse import urlencode
from requests.exceptions import ConnectionError
from bs4 import BeautifulSoup
from json.decoder import JSONDecodeError
from hashlib import md5
from config import *
from multiprocessing import Pool
import requests
import json
import re
import os
import pymongo
client = pymongo.MongoClient(MONGO_URL, connect=False)
db = client[MONGO_DB]
def get_page_index(url, headers):
"""
作用:返回页面源码
url:请求地址
headers:请求头信息
"""
try:
response = requests.get(url, headers=headers)
# 判断是否访问成功
if response.status_code == 200:
return response.text
except ConnectionError:
print('Erroe occured')
return None
def parse_page_index(html):
"""
作用:解析出标题URL地址
html:网页源码
"""
try:
# 将数据转为json格式
data = json.loads(html)
# print(data)
# 判断data是否为空,以及data字典中是否有data这个键
if data and 'data' in data.keys():
for item in data.get('data'):
if item.get('article_url'):
yield item.get('article_url')
except JSONDecodeError:
pass
def get_page_detail(url, headers):
"""
作用:返回标题URL网页源码
url:标题URL地址
headers:请求头信息
"""
try:
response = requests.get(url, headers=headers)
# 判断是否访问成功
if response.status_code == 200:
return response.text
except ConnectionError:
print('Error occured')
return None
def parse_page_detail(html, url):
"""
作用:解析标题URL地址的每个图片链接
html:标题URL网页源码
url:标题URL地址
"""
# 利用BeautifulSoup找到title的文本
soup = BeautifulSoup(html, 'lxml')
title = soup.title.text
# 利用正则找到每个下载图片的地址
images_pattern = re.compile('gallery: JSON.parse\("(.*)"\)', re.S)
result = images_pattern.search(html)
# print(result)
if result:
data = json.loads(result.group(1).replace('\\', ''))
# 提取出sub_images键的键值
if data and 'sub_images' in data.keys():
sub_images = data.get('sub_images')
# 使用列表生成式拿到每个图片URL
images = [item.get('url') for item in sub_images]
for image in images:
# 下载图片
download_image(image)
# 将return的结果保存至MongoDB中
return {
'title': title,
'url': url,
'images': images
}
def download_image(url):
"""
作用:返回图片URL源码
url:图片URL地址
"""
print('Downloading', url)
try:
response = requests.get(url)
# 判断是否访问成功
if response.status_code == 200:
save_image(response.content)
return None
except ConnectionError:
return None
def save_image(content):
"""
作用:保存图像文件
content:图像二进制数据
"""
# 使用md5加密内容,生成图像名称
file_path = '{0}/{1}.{2}'.format(os.getcwd(), md5(content).hexdigest(), 'jpg')
print(file_path)
# 判断该文件名是否存在
if not os.path.exists(file_path):
with open(file_path, 'wb') as f:
f.write(content)
f.close()
def save_to_mongo(result):
"""
作用:保存数据至MongoDB数据库
result:包括图片标题,请求地址,图像地址
"""
if db[MONGO_TABLE].insert(result):
print('Successfully Saved to Mongo', result)
return True
return False
def jiepai_Spider(offset):
"""
作用:整个爬虫调度器
offset:位置参数
"""
headers = {
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.62 Safari/537.36'
}
data = {
"offset": offset,
"format": "json",
"keyword": "街拍",
"autoload": "true",
"count": "20",
"cur_tab": "1",
"from": "search_tab"
}
# 通过urlencode构造请求URL
url = 'https://www.toutiao.com/search_content/' + '?' + urlencode(data)
# 测试url
# print(url)
# 获取页面源码
html = get_page_index(url, headers)
# 解析HTML,获得链接地址
for url in parse_page_index(html):
# print(url)
# 获得每个链接地址的HTML
html = get_page_detail(url, headers)
result = parse_page_detail(html, url)
# 判断result是否为空,保存至MongoDB数据库中
if result:
save_to_mongo(result)
if __name__ == "__main__":
# 创建进程池
pool = Pool()
groups = ([x * 20 for x in range(GROUP_START, GROUP_END + 1)])
pool.map(jiepai_Spider, groups)
pool.close()
# 等待pool中所有子进程执行完成,必须放在close语句之后
pool.join()
总结思考
1.在利用正则进行匹配的时候如果原文有‘(“ ”)',这类符号时那么你在进行正则表达式书写的时候应该在前面加'\'。按理应该也可以使用原始字符串r,可是我用完最后在匹配的时候返回的是None,留个疑问
pattern = re.compile('gallery: JSON\.parse\("(.*?)"\),', re.S)
2. db = client[MONGO_DB]这里应该是方括号而不是 ( ),否则无法正常访问数据库
3. 在Google浏览器中找不到图片url,然后使用的是火狐浏览器来回查看