上次爬了教務(wù)處的成績,接下來想去爬一爬知乎娘,好像大家都很喜歡爬知乎娘,GitHub上貌似已經(jīng)有人把獲取各種知乎數(shù)據(jù)的操作封裝好了:zhihu-python ,但是良辰表示還是想自己試一試,我就爬點簡單的,難度大的我也不會,我瞄準的是我自己關(guān)注的人的數(shù)據(jù),差不多就這些東西:
那么下面開始分析,要拿到這個數(shù)據(jù)肯定是要先登陸的,那么先找到登陸界面:關(guān)注者 / 提問 / 回答 / 贊同




現(xiàn)在理一下思路,我們要登錄知乎要傳遞的數(shù)據(jù)如下:
- _xsrf()用于防偽登陸
- password- captcha 驗證碼
- phone_num/email 不同登陸方式傳遞的東西不同
phone_num/email以及passsword都需要自己輸入,這個好辦,我們需要解決如何獲取 _xsrf和captcha 的問題,先解決 _xsrf,這個更簡單一點,我們在知乎登陸頁面 右鍵查看網(wǎng)頁源代碼,直接搜_xsrf:
<div class="view view-signin" data-za-module="SignInForm">
<form><input type="hidden" name="_xsrf"
value="cf1ee28f15cea5dba3243a1c31a1b284"/><div class="group-inputs">
我們要做的就是解析出這個元素,直接上代碼:
# 構(gòu)造 Request headers
# 登陸的url地址
logn_url = 'http://www.zhihu.com/#signin'
session = requests.session()headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko)
Chrome/52.0.2743.82 Safari/537.36',}
content = session.get(logn_url, headers=headers).content
soup = BeautifulSoup(content, 'html.parser')def getxsrf():
return soup.find('input', attrs={'name': "_xsrf"})['value']
下面獲取驗證碼, 我發(fā)現(xiàn)無論是使用哪一種登陸方式,Chrome的開發(fā)者面板都會有這么一行:
當我把這個Request URL輸入瀏覽器中就會轉(zhuǎn)到這個驗證碼圖片的界面,說明這個驗證碼是我們加載這個頁面時候,瀏覽器向服務(wù)器發(fā)出請求然后下載下來的,那么現(xiàn)在我們有了URL地址:

try:
from PIL import Image
except:
pass
# 獲取驗證碼
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
這里獲取驗證碼的代碼來自GitHub上的fuck-login項目,在此表示感謝,接下里就是寫一個方法判斷用戶是使用了哪一種登陸方式,然后傳遞相應(yīng)的數(shù)據(jù):
def login(secret, account):
# 通過輸入的用戶名判斷是否是手機號
if re.match(r"^1\d{10}$", account):
print("手機號登錄 \n")
post_url = 'http://www.zhihu.com/login/phone_num'
postdata = {
'_xsrf': getxsrf(),
'password': secret,
'remember_me': 'true',
'phone_num': account,
}
else:
print("郵箱登錄 \n")
post_url = 'http://www.zhihu.com/login/email'
postdata = {
'_xsrf': getxsrf(),
'password': secret,
'remember_me': 'true',
'email': account,
}
try:
# 不需要驗證碼直接登錄成功
login_page = session.post(post_url, data=postdata, headers=headers)
login_code = login_page.text
print(login_page.status)
print(login_code)
except:
# 需要輸入驗證碼后才能登錄成功
postdata["captcha"] = get_captcha()
login_page = session.post(post_url, data=postdata, headers=headers)
login_code = eval(login_page.text)
print(login_code['msg'])
# 這部分代碼同樣來自[**fuck-login**](https://github.com/xchaoinfo/fuck-login)項目,我偷了很多懶,囧
這個時候判斷是否登陸成功可以隨便查看一個頁面發(fā)送GET請求看是否返回200,這里就不多說,接下來才是關(guān)鍵,為了獲取我關(guān)注的人的信息,先打開開發(fā)者面板,這個地方打鉤:
因為網(wǎng)頁有時候會跳轉(zhuǎn),這里打鉤之后新跳轉(zhuǎn)的頁面的信息就不會覆蓋之前接受到的信息,然后找到我關(guān)注的人,URL地址是:
最開始我走了彎路,我以為直接在這個頁面解析出我關(guān)注的人的信息就行,所以一開始我是這么做的,查看網(wǎng)頁源代碼,這里隨便找一個我關(guān)注的人的信息:
<a title="死者代言人"
data-hovercard="p$t$forensic"
class="zm-item-link-avatar"
href="/people/forensic">

</a>
<div class="zm-list-content-medium">
<h2 class="zm-list-content-title"><a data-hovercard="p$t$forensic" class="zg-link author-link" title="死者代言人"
>死者代言人</a><span class="icon icon-badge-best_answerer icon-badge" data-tooltip="s$b$優(yōu)秀回答者"></span></h2>
<div class="ellipsis">
<span class="badge-summary">優(yōu)秀回答者</span>
<span class="bio">不養(yǎng)喵的愛喵法醫(yī)。</span>
</div>
<div class="details zg-gray">
<a target="_blank" href="/people/forensic/followers" class="zg-link-gray-normal">35968 關(guān)注者</a>
/
<a target="_blank" href="/people/forensic/asks" class="zg-link-gray-normal">2 提問</a>
/
<a target="_blank" href="/people/forensic/answers" class="zg-link-gray-normal">305 回答</a>
/
<a target="_blank" href="/people/forensic" class="zg-link-gray-normal">51278 贊同</a>
</div>
然后在對比另一條:
<a title="陳亦飄"
data-hovercard="p$t$chen-yi-piao"
class="zm-item-link-avatar"
href="/people/chen-yi-piao">

</a>
<div class="zm-list-content-medium">
<h2 class="zm-list-content-title"><a data-hovercard="p$t$chen-yi-piao" class="zg-link author-link" title="陳亦飄"
>陳亦飄</a></h2>
<div class="ellipsis">
<span class="bio">音樂和電影是我的愛與慈悲</span>
</div>
<div class="details zg-gray">
<a target="_blank" href="/people/chen-yi-piao/followers" class="zg-link-gray-normal">74469 關(guān)注者</a>
/
<a target="_blank" href="/people/chen-yi-piao/asks" class="zg-link-gray-normal">0 提問</a>
/
<a target="_blank" href="/people/chen-yi-piao/answers" class="zg-link-gray-normal">80 回答</a>
/
<a target="_blank" href="/people/chen-yi-piao" class="zg-link-gray-normal">315971 贊同</a>
</div>
要分解出這些元素,只要找出他們的共同點,當時機智如我一眼就發(fā)現(xiàn)每一個我關(guān)注的人的名字都有這么一行元素:
class="zm-item-link-avatar"
而且都包含在一個<a></a>標簽里面,這就好辦了,使用BeautifulSoup先分解出每一個我關(guān)注的人的名字:
def getdetial():
followees_url = 'https://www.zhihu.com/people/GitSmile/followees'
followees_headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36',
'Referer': 'https://www.zhihu.com/people/GitSmile/about',
'Upgrade-Insecure-Requests': '1',
'Accept-Encoding': 'gzip, deflate, sdch, br'
}
myfollowees = session.get(followees_url, headers=followees_headers)
mysoup = BeautifulSoup(myfollowees.content, 'html.parser')
print(mysoup.find('span', attrs={'class': 'zm-profile-section-name'}).text)
然后繼續(xù)觀察,突破點依然在這個<a></a>標簽,拿"陳亦飄"的信息做個例子,看官們看這里面是不是有個href="/people/chen-yi-piao,然后看一下她的相關(guān)信息:
<a target="_blank" href="/people/chen-yi-piao/followers" class="zg-link-gray-normal">74469 關(guān)注者</a>
/
<a target="_blank" href="/people/chen-yi-piao/asks" class="zg-link-gray-normal">0 提問</a>
/
<a target="_blank" href="/people/chen-yi-piao/answers" class="zg-link-gray-normal">80 回答</a>
/
<a target="_blank" href="/people/chen-yi-piao" class="zg-link-gray-normal">315971 贊同</a>
發(fā)現(xiàn)沒有,每一個信息里面的href元素都是我們最開始看到的/people/chen-yi-piao元素加上一些字符串組成的,那么我就可以在這上面做點文章:
獲取詳細信息
def getdetial():
followees_url = 'https://www.zhihu.com/people/GitSmile/followees'
followees_headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36',
'Referer': 'https://www.zhihu.com/people/GitSmile/about',
'Upgrade-Insecure-Requests': '1',
'Accept-Encoding': 'gzip, deflate, sdch, br'
}
myfollowees = session.get(followees_url, headers=followees_headers)
mysoup = BeautifulSoup(myfollowees.content, 'html.parser')
print(mysoup.find('span', attrs={'class': 'zm-profile-section-name'}).text)
for result in mysoup.findAll('a', attrs={'class': 'zm-item-link-avatar'}):
print(result.get('title'))
# 解析出href元素信息
href = str(result.get('href'))
# 關(guān)注者
print(mysoup.find('a', attrs={'href': href + '/followers'}).text)
# 提問
print(mysoup.find('a', attrs={'href': href + '/asks'}).text)
# 回答
print(mysoup.find('a', attrs={'href': href + '/answers'}).text)
# 贊同
print(mysoup.find('a', attrs={'href': href, 'class': 'zg-link-gray-normal'}).text + '\n')
上面的代碼應(yīng)該很簡單了,看一看就能看懂,然后就我信心滿滿地在PyCharm上輸出的時候,發(fā)現(xiàn)來來回回只輸出20條信息,也就是說,我關(guān)注了26人,但是控制臺只輸出了20個人的信息,然后我就上網(wǎng)查,發(fā)現(xiàn)不止一個人有我這樣的疑問,當然也多虧了前人踩坑,網(wǎng)上給出來的答案是知乎獲取關(guān)注的人的時候使用了AJAX技術(shù),也就是動態(tài)加載,但是這一部分代碼不會再網(wǎng)頁Html代碼中顯示出來,所以為了獲取其他關(guān)注的人的信息我這里要另辟蹊徑。
然后我翻看我關(guān)注的人的信息的時候,在開發(fā)者面板抓到這么一條POST信息:

這條POST之后下面刷出來的圖片是我關(guān)注的人的頭像并且這些頭像在我之前看到的20條數(shù)據(jù)里面是沒有的,加上我總共就關(guān)注了27個人,所以我有理由相信這個Post就是瀏覽器向服務(wù)器發(fā)送請求的Post,看一下Post的信息:

一個偏移量(offset),一個哈希值(hash_id)外加一個"order_by":"created"的鍵值對,這里偏移量很好理解,這個"hash_id"據(jù)我多次登陸發(fā)現(xiàn)是一個不變的值,或者說每一臺電腦或許精確一點每一個瀏覽器都會有這么一個給定的值,照抄,那么改進后的代碼如下:
# 獲取所有關(guān)注的人的信息
def getallview():
nums = 27 # 這個是我關(guān)注的人數(shù)
followees_headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36',
'Referer': 'https://www.zhihu.com/people/GitSmile/followees',
'Origin': 'https://www.zhihu.com',
'Accept-Encoding': 'gzip, deflate, br',
'CG - Sid': '57226ad5 - 793b - 4a9d - 9791 - 2a9a17e682ef',
'Accept': '* / *'
}
# 序號
count = 0
for index in range(0, nums):
fo_url = 'https://www.zhihu.com/node/ProfileFolloweesListV2'
m_data = {
'method': 'next',
'params': '{"offset":' + str(
index) + ',"order_by":"created","hash_id":"de2cb64bc1afe59cf8a6e456ee5eaebc"}',
'_xsrf': str(getxsrf())
}
result = session.post(fo_url, data=m_data, headers=followees_headers)
dic = json.loads(result.content.decode('utf-8'))
li = dic['msg'][0]
mysoup = BeautifulSoup(li, 'html.parser')
for result in mysoup.findAll('a', attrs={'class': 'zm-item-link-avatar'}):
print(index + 1)
print(result.get('title'))
href = str(result.get('href'))
print(mysoup.find('a', attrs={'href': href + '/followers'}).text)
print(mysoup.find('a', attrs={'href': href + '/asks'}).text)
print(mysoup.find('a', attrs={'href': href + '/answers'}).text)
print(mysoup.find('a', attrs={'href': href, 'class': 'zg-link-gray-normal'}).text + '\n')
count += 1
print('一共關(guān)注了 %d人' % count)
放上程序的入口:
if __name__ == '__main__':
if isLogin():
print('您已經(jīng)登錄')
else:
account = input('請輸入你的用戶名\n> ')
secret = input("請輸入你的密碼\n> ")
login(secret, account)
getallview()
看一下實際效果:

寫在結(jié)尾
雖然簡單,但是提供了一些初學(xué)者的思路,下一次準備爬一些知乎上的圖片,好像很多人都熱衷于這種事,嘻嘻,荊軻刺秦王。