输入关键字 抓取所有网页(《Python爬虫:模拟登录知乎》分析页面请求准备准备)
优采云 发布时间: 2022-01-31 19:27输入关键字 抓取所有网页(《Python爬虫:模拟登录知乎》分析页面请求准备准备)
接上一篇文章《Python爬虫:模拟登录知乎》。
分析页面请求
LZ 将从 知乎 的主题页面中获取所有主题及其结构。页面获取主题的ajax请求有两种,分别是“显示子主题”和“加载更多”,都是一次输出10个主题。请求网址如下:
https://www.zhihu.com/topic/19776749/organize/entire?child=&parent=19552706
其中 child 和 parent 是主题的 ID,“显示子主题”只需要 parent 参数,“加载更多”需要两个参数。如果两个参数都不存在,则表示“根主题”。
两种请求的响应数据都是json格式,结构类似,只是msg中的最后一个元素不同。
抓取逻辑
知乎主题结构是分层的。为了描述方便,“根主题”的子主题称为一级主题,一级主题的子主题为二级主题,以此类推……
为了抓取所有主题,流程应该是:从根主题开始,抓取所有一级主题并存储,然后依次分析每个一级主题,如果有子主题,则抓取它的所有子主题(次要)和存储,然后......循环继续下去。
根据流程,需要实现以下三个主要功能:
多谈谈主题存储。因为LZ要保存主题结构,“加载更多”时需要使用父主题ID,所以LZ将每个主题记录为一个列表,其中收录四个元素:主题名称、主题ID、父主题ID、有无状态子主题(bool类型),如['entity', '19778287', '19776749', 1],去重时,四个元素相同,则认为是重复。
代码
在代码中,第一段是模拟登录知乎。运行时,输入验证码后取数据,完成后数据保存到本地文件。因为运行时间比较长(1小时左右),所以增加了一些状态提示。
# all_topics.py
import requests
from bs4 import BeautifulSoup
# session变量
s = requests.Session()
headers = {
"Accept": "*/*",
"Accept-Encoding": "gzip,deflate",
"Accept-Language": "en-US,en;q=0.8,zh-TW;q=0.6,zh;q=0.4",
"Connection": "keep-alive",
"Content-Type":" application/x-www-form-urlencoded; charset=UTF-8",
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/38.0.2125.111 Safari/537.36",
"Referer": "http://www.zhihu.com/"
}
_xsrf = ''
def set_xsrf():
global _xsrf
html = s.get('http://www.zhihu.com/', headers=headers) # GET请求,获取一个响应对象
soup = BeautifulSoup(html.text,'lxml-xml') # 使用BeautifulSoup解析HTML
_xsrf = soup.findAll(type='hidden')[0]['value']
def get_captcha():
# 获取验证码图片并保存在本地
captcha_url = 'http://www.zhihu.com/captcha.gif'
captcha = s.get(captcha_url, stream=True, headers=headers)
with open('captcha.gif', 'wb') as f:
for line in captcha.iter_content(10):
f.write(line)
f.close()
# 输入获取到的验证码,并入变量captch_str
print('输入验证码:', end='')
captcha_str = input()
return captcha_str.strip()
# 登录请求
def login():
global s
global _xsrf
set_xsrf()
captcha = get_captcha()
data = {
'_xsrf': _xsrf,
'email': '654408619@qq.com',
'password': 'mftx1029',
'remember_me': True,
'captcha': captcha
}
r = s.post(url='http://www.zhihu.com/login/email', data=data, headers=headers)
return r
all_topics = [['根话题', '19776749', '', 1]] # 保存所有的话题,初始状态保存根话题
has_children = [['根话题', '19776749', '', 1]] # 有子话题,但还没有抓取其子话题。初始状态保存根话题
cnt_a = 0 # all_topics元素计数
cnt_h = 0 # has_children元素计数
# 获取一次request的json数据
def get_json(child_id='', parent_id=''):
global s
global headers
url = 'https://www.zhihu.com/topic/19776749/organize/entire'
data = {'child': child_id, 'parent': parent_id, '_xsrf': _xsrf}
res = s.post(url,data=data, headers=headers)
json_dict = res.json()
return json_dict
# 解析json数据,获取子话题及「加载更多」的信息
def json_process(json_dict):
single_dict = {} # 保存下面的children和more
children = [] # 保存子话题
more = [] # 保存加载更多的信息
parent_id = json_dict['msg'][0][2]
if len(json_dict['msg'][1]) == 11:
more_child_id = json_dict['msg'][1][9][0][2]
more = [more_child_id, parent_id]
for i in json_dict['msg'][1][:10]:
child_name, child_id = i[0][1], i[0][2]
if i[1]:
children.append([child_name, child_id, parent_id, 1])
else:
children.append([child_name, child_id, parent_id, 0])
single_dict['children'] = children
single_dict['more'] = more
return single_dict
# 获取一个话题的所有子话题
def get_children(parent_id='', parent_name=''):
global cnt_a
global cnt_h
children = []
more = ['', parent_id]
while more:
json_dict = get_json(child_id=more[0], parent_id=more[1])
single_dict = json_process(json_dict)
children += single_dict['children']
more = single_dict['more']
# 用140个空格清除上次的提示
print(' '*140, end='\r')
# 终端提示抓取状态
print('已获取{}个话题。还有{}个话题排队当中,正在抓取「{}」的第{} 个子话题...'.format(\
cnt_a, cnt_h, str(parent_name).encode('gbk', 'ignore').decode('gbk'), len(children)), end='\r')
return children
# 工作函数,抓取全部话题并保存为文件
def work():
global cnt_h
global cnt_a
login()
while has_children:
first_child = has_children.pop(0)
children = get_children(first_child[1],first_child[0])
for c in children:
# 去重并添加到all_topics和has_topics,后者需要再判断下是否有子话题
if c not in all_topics:
all_topics.append(c)
if c[-1] == 1:
has_children.append(c)
cnt_a = len(all_topics)
cnt_h = len(has_children)
# 存入文件
for item in all_topics:
with open('all_topics.txt', 'a', encoding='utf-8') as f:
string = str(item[0]) + '\t' + str(item[1]) + '\t' + str(item[2]) + '\t' + str(item[3]) + '\n'
f.write(string)
if __name__ == "__main__":
work()
运行状态如下:
E:\@coding\python>python all_topics.py
输入验证码:hbfu
已获取53个话题。还有46个话题排队当中,正在抓取「「未归类」话题」的第1600 个子话题...
LZ是昨天运行的一个程序(2016-02-04),一共捕获了46272个话题。