加入了几个知识星球,鉴于精华帖阅读体验较差,想一次性爬下来,细细品读。于是开始了此次的爬虫任务:
网上找类似教程
原本打算网页端爬取,结果没找到合适的。(刚才又搜了下,好像这个是我真正想要的,看来还是的明确目标,多多搜索)
一切从手机APP抓取实战——知识星球开始的。
按照基本步骤:
- 配置Charles(顺利)
- APP抓包(因华为手机问题,无法信任已安装证书,改为IPad)
- 模拟请求(图片问题卡壳了好久)
话不多说,上代码吧。
事先声明,拙劣的代码别质疑,质疑就是我菜。
先导入必要的模块
import requests
import os
import time
import urllib.request
import urllib
requests.packages.urllib3.disable_warnings() #防止警告
import ssl
ssl._create_default_https_context = ssl._create_unverified_context #全局取消证书验证
from datetime import date,timedelta
其中关于ssl._create_default_https_context = ssl._create_unverified_context #取消证书验证这个解决了我烦恼一时的图片无法下载问题。
使用python3.6进行 request请求时【出现问题的原因是“SSL:CERTIFICATE_VERIFY_FAILED”】的类似错误
请求头
等同于Pad访问,从Charles获取,重点是X-Request-Id和Authorization。
headers={
'Host': 'api.zsxq.com' ,
'X-Version': '1.10.30' ,
'Accept': '*/*' ,
'User-Agent': 'xiaomiquan/4.7.0 iOS/Pad/13.1.3',
'Accept-Language': 'zh-Hans-CN;q=1',
'X-Request-Id': 'XXXXXXXXXXX',
'Authorization': 'XXXXXXXXXXX',
}
精华帖日期范围
start_to_end = []
#给定相差的天数,返回从开始到结束的全部天
def get_range(delta_days):
start = date(2017,12,2) #开始日期
for i in range(delta_days):
start_to_end.append(start)
start += timedelta(days=1)
return start_to_end
date_range =get_range(543) #从开始到543天后的每一天集合
str_daterange = [i.strftime(format='%Y %m %d') for i in date_range] #日期范围
right_date = str_daterange[1:] #左 用于嵌入网址
left_date = str_daterange[:-1] #右
重点:从给定链接下载精华帖的文字、评论及图片
据观察试验,链接形式如下:https://api.zsxq.com/v1.10/groups/2123521721/topics?count=20&begin_time={开始年份}%2D{月}%2D{日} T00%3A00%3A00%2E000%2B0800&end_time={结束年份}%2D{月}%2D{日}T23%3A59%3A59%2E000%2B0800&scope=digests'.
从某年某月某日到某年月日,按时间阶段搜此区间内的所有帖子。
但每条链接最大显示数量40个。
原本打算以周为间隔,每7天集结成一个文件夹。但存在2个问题:
- 昨天晚上,运行一段时间发现,有的文件夹空空如也。经查,发现是日期转换搞的鬼,date形式的2019-01-01转换为字符串成了2019-1-1,于是链接里月、日哪里少了0,因此无效链接,无法爬取数据;
- 大多数周的帖子总数远超40,甚至单日就将近40;
- 解决方案:1.用strftime函数,确保字符串转化后依然带0;2改成以日为单位,每天一个文件夹。
def crawl_from_url(url,dir_name): #传入链接和一个文件夹名称
if not os.path.exists(dir_name): #是否存在名为dir_name的文件夹
os.makedirs(dir_name) #没有就新建个
res = requests.get(url, headers=headers, verify=False)
try:
content = res.json()['resp_data']['topics']
my_text = []
m = n = 0
for i in content:
comments=[]
img = 0
#爬取主题文字和评论
talk = i['talk']
text =talk['text']
try:
for j in i['show_comments']:
comments.append(j['text'])
my_text.append('\r\n'+'\r\n'+'【第{}个精华帖】'】[{}]'.format(n,i['create_time'])+'\r\n'+'\r\n'+'\r\n'+text+'\r\n')
my_text = my_text+['\r\n'+'\r\n'+'【评论:】''\r\n'+'\r\n']+comments
except KeyError:
print('这个帖子没评论')
print('第{}个精华帖下载完成'.format(n))
n +=1
try: #爬取图片
for j in i['talk']['images']:
time.sleep(0.5)
dir = dir_name+'//'+str(n)+'_'+str(m)+'_'+'.jpg'
urllib.request.urlretrieve(j['large']['url'], dir)
print('下载成功第{}个精华帖的第{}个图片'.format(n,m))
m +=1
except KeyError:
print('第{}个精华帖没有图哦'.format(n))
n +=1
print('{}全部下载完成!'.format(dir_name))
txt_path = dir_name+'//'+'dir_name.txt'
with open(txt_path, 'w',encoding='utf-8') as f:
f.writelines(my_text)
except KeyError:
print('当前日期没有帖子,下一个~')
这个函数是整个代码的核心,主要就是从json中找到文字、图片、评论等关系,然后一一提取即可。
可以注意到有好几个try...except...结构,这也是其中一个大坑:有的日期没帖子,有的帖子没评论,有的帖子没图,因此直接爬取会遭遇KeyError,用了try...except...后就可以跳过这些不存在的继续往下遍历。
还有个小坑是,往txt文件中写入换行符\n要带上\r\n,转义字符。
按每天来下载帖子
left_date: [2018 01 01... ... 2019 11 07]
right_date: [2018 01 02 ... ... 2019 11 08]
将left_date和right_date的年月日插入网址,形成链接,并以left_date日期为名新建文件夹。
for l,r in zip(left_date, right_date):
url_by_day = 'https://api.zsxq.com/v1.10/groups/222588821821/topics?count=20&begin_time={}%2D{}%2D{}T00%3A00%3A00%2E000%2B0800&end_time=
{}%2D{}%2D{}T23%3A59%3A59%2E000%2B0800&scope=digests'.format(l[0:4],l[5:7],l[8:10],r[0:4],r[5:7],r[8:10])
crawl_from_url(url_by_day,l[0:4]+l[5:7]+l[8:10])
[图片上传失败...(image-afd38-1573222234053)]
小结
学的零零散散的Python&爬虫,第一次派上用场,边摸索边试错,虽不甚完美,但自我感觉可以及格啦,希望以后继续多学、多用!
- 报错信息,直接谷歌百度,依旧是最直接有效的解决方式;
- 不要急于去运行代码,准备个纸笔,多画画写写,厘清逻辑,多看几遍,自我检查,更有助于少走弯路、节省时间、提高效率。