这次这玩意折腾我了半天,终于成功了/(ㄒoㄒ)/~~。急需把中间遇到的问题详细记录下来宣泄一下
在前面代码的基础上,想尝试将知乎上关注某话题的用户头像爬取下来。在参考了知乎大神们的回答之后,修改完善了自己的代码,终于可以运行成功了。相比较前面的内容,其实只多了一个offset概念(就是知乎用来加载后续用户头像的插件?每次加载20个,通过post传递)。其他就是requests,bs4的内容。但不得不说,知乎比糗百折腾多了,在享受了无数的403之后才最终把头像下载下来╮(╯▽╰)╭
需要引用的包
# coding : UFT-8
import requests
import random
import time
import os
import os.path
from bs4 import BeautifulSoup
try:
import cookielib
except:
import http.cookiejar as cookielib
from PIL import Image
本来快写完了的。。。结果手贱关了(蓝瘦,香菇),只能从来。requests是用来完成HTTP协议的;bs4是用来解析提取抓取页面的信息;cookies是用来处理cookies文件;PIL用来处理验证码图片。
基本信息设置
#该程序是爬取知乎美妆话题下关注人头像
#登录知乎
#报文头设置!
headers = {
'Accept': '*/*',
'Accept-Encoding': 'gzip, deflate',
'Accept-Language': 'zh-CN,zh;q=0.8',
'Connection': 'keep-alive',
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
'Host': 'www.zhihu.com',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.87 Safari/537.36',
'X-Requested-With': 'XMLHttpRequest'
}
#知乎主页
HostUrl = 'http://www.zhihu.com'
#摘取信息的页面
FollowerUrl = 'https://www.zhihu.com/question/22390958/followers'
#登录url
LoginUrl = 'https://www.zhihu.com/login/email'
timeout = random.choice(range(60, 180))
报文头很重要!如果访问时不添加或者设计的简单很有可能会被知乎403。。。这里可以使用Chrome的F12开发者项目来查看真正浏览器访问时的报文头,从而参考设计自己的报文头。
session以及cookies
session = requests.session()#创建一个session
#使用cookie信息加载
session.cookies = cookielib.LWPCookieJar(filename='cookies')
try:
session.cookies.load(ignore_discard=True)
except:
print("cookie加载失败")
这里大改意思就是创建一个会话,然后将保存到本地的cookies文件读入。如果读入成功,则可以实现免登陆;若失败则需要输入用户信息进行登录。关于cookies的处理,今后再看(=_=网上抄的)
知乎登录
这里和前面一节基本相同,只是对部分函数进行了简单的修改
- 测试是否已经登录
def isLogin():
# 通过查看用户个人信息来判断是否已经登录
url = "https://www.zhihu.com/settings/profile"
login_code = session.get(url,allow_redirects=False,headers = headers).status_code
print(login_code)
if int(x=login_code) == 200:
return True
else:
return False
再加载完cookies后,访问用户个人信息。若访问成功说明登录成功;否则重新登录。
- 用户验证登录
#验证登录
def login(url):
login_data = {
'_xsrf' : get_xsrf(url),
'password' : '密码(⊙o⊙)',
'remember_me' : 'true',
'email' : '邮箱~\(≧▽≦)/~'
}
try:#不需要验证码登录
repr=session.post(LoginUrl, data=login_data, headers=headers)
print(repr)
except:#需要验证码登录
login_data['captcha_type'] = 'cn'
login_data['captcha'] = get_captcha()
repr = session.post(LoginUrl,data=login_data,headers=headers)
print(repr)
session.cookies.save()
这里分两种登录情况,可以通过Chrome查看两种情况下需要post的信息。需要验证码登录时,post的数据会加上captcha_type和captcha,其中captcha是用户输入的验证码。另外!!_xsrf在整个过程中很重要!!这里从Chrome浏览器的F12可以看到需要post该信息,但到后面抓取信息时就变成了磨人的小妖精/(ㄒoㄒ)/~~。在登录成功后,将cookies保存到本地,方便下一次免登录。
获取_xsrf
#获取_xsrf
def get_xsrf(url):
res = session.get(url,headers = headers,timeout=timeout).content
_xsrf = BeautifulSoup(res,'html.parser').find('input',attrs={'name':'_xsrf'})['value']
print(_xsrf)
return _xsrf
和前面一节知乎登录获取方式没啥区别,只是单独提出做了一个函数,方便后面程序的调用。
解析验证码
#解析验证码
def get_captcha():
t = str(int(time.time() * 1000))
captcha_url = 'http://www.zhihu.com/captcha.gif?r=' + t + "&type=login"
r = session.get(captcha_url, headers=headers)
with open('captcha.jpg', 'wb') as f:
f.write(r.content)
f.close()
# 用pillow 的 Image 显示验证码
# 如果没有安装 pillow 到源代码所在的目录去找到验证码然后手动输入
try:
im = Image.open('captcha.jpg')
im.show()
im.close()
except:
print(u'请到 %s 目录找到captcha.jpg 手动输入' % os.path.abspath('captcha.jpg'))
captcha = input("please input the captcha\n>")
return captcha
获取用户头像
这里就是折腾我最久的地方,收到了无数的403╮(╯▽╰)╭!知乎用offset加载用户的头像,每次加载20个,使用的post传递数据。
- 获取页面内容
#加载页面获取html内容,主要offset
def get_html(_xsrf):
url = FollowerUrl
res = session.get(url,headers=headers)#进入美妆话题
pictures = get_pic(res.text)#获取当前页面头像
download_pic(pictures)#下载头像
time.sleep(random.choice(range(2,5)))#睡眠函数,防止爬取过快封ip
print(_xsrf)
headers['X-Xsrftoken'] = _xsrf
headers['Referer'] = url
for x in range(20,100,20):#x从20-100,每次加20,即20,40,60,80,100
#需要post的值
form_data = {
'start' : '0',
'offset' : str(x),
'_xsrf' : _xsrf
}
print(str(x))
try:
res= session.post(url,data=form_data,headers=headers)
print(res)
except:
print("offset加载失败")
else:
content = res.text
html = eval(content)['msg'][1]#获取服务器返回的值
pictures = get_pic(html)
download_pic(pictures)
time.sleep(random.choice(range(1,5)))
这里有几点需要注意:
- post的值需要包含_xsrf,这个用Chrome查看时是看不到有这个数据传输的,只有start和offset(简直心机(#‵′)凸)。刚开始只包含了start和offset进行post,总是返回403。然后看到报文头里面有xsrf的值,就试着带着这玩意传递了,竟然真是这个原因!(艹皿艹 )
- 使用cookies登录方式,这里传递的form_data中的_xsrf并不是登录时的_xsrf值。在传递数据包含了_xsrf后,使用用户输入信息登录的方式可以访问到结果。但是用cookies登录方式,这里post总是返回403。之前在我用Chrome查看时,发现使用cookies登录方式后访问该页面的_xsrf值与登录时_xsrf一致,因此我错误的将登录时获取到的_xsrf值像cookies一样以文件的方式保存下来了,当用cookies方式登录时,就从本地读取_xsrf值放入form_data中。但是!!根本不是这样,使用cookies登录后,需要重新获取_xsrf的值!根本不是登录时的值。不知道为什么和浏览器中看到的不一样/(ㄒoㄒ)/~~。这里真的折腾了好久呀。
- 这里post成功后返回的值并不是整个页面,而仅仅是这20个头像那一部分的内容,而且返回内容和get方式返回还是有点区别的。post的返回内容有点类似于dict,但不是一个dict,需要使用eval()函数格式化一下。
- 解析获取的html
#根据content内容提取头像
def get_pic(content):
pictures = []
bs4 = BeautifulSoup(content, "html.parser")
users = bs4.find_all(class_='zm-profile-card zm-profile-section-item zg-clear no-hovercard')#查找所有关注者
for user in users:
temp = []
username = user.find('a').get('title')#查找关注者姓名
temp.append(username)
picUrl = user.find('img').get('src')#查找用户头像url
picUrl = picUrl.replace('\\','')
temp.append(picUrl)
print(temp)
pictures.append(temp)
return pictures
这里需要注意的就是post返回的url含了“\”符号,需要把这个去掉才是正确的url。
- 下载头像
def download_pic(pictures):
if not os.path.exists('pic'):
os.makedirs('pic')
for picture in pictures:
try:
r= requests.get(picture[1],headers)
except:
print("图片下载失败")
else:
filename = picture[0]+'.jpg'
path = "pic/" + filename
with open(path,'wb') as f:
f.write(r.content)
time.sleep(random.choice(range(1,3)))
与前面下载糗百头像代码基本一样
主函数
if __name__ == '__main__':
if isLogin():
print("已经登录")
else:
print("重新登录")
login(HostUrl)
_xsrf = get_xsrf(HostUrl)
get_html(_xsrf)
这里放上源代码(⊙o⊙)