如何用爬蟲爬取知乎專欄信息?

知乎專欄和知乎總站不一樣,是基於AngularJS架構的,所有專欄網頁源代碼根本是一樣的,請問怎樣爬取特定的信息呢?
舉個例子,想獲取每個專欄的關注數。


知乎專欄API來一發?

舉倆栗子:

URI: http://zhuanlan.zhihu.com/api/columns/jixin GET/HTTP 1.1

訪問上面的URI,瀏覽器地址欄里直接粘貼也行,得到的返回JSON數據就包含了專欄關注數。

不管AngularJS還是其它架構,都是服務端的東西,再天花亂墜的服務端架構,到了客戶端終究逃不脫HTTP協議,至少目前來說還是如此。

順便分享一些關於爬知乎的東西。
目前來說還沒有官方API的支持,可能最有用的也就是用戶的「個性網址」(好彆扭,下稱UID)了,譬如黃繼新老師的UID: jixin,不過可以由用戶本人修改,但每個用戶一定唯一。

以{{%UID}}代替相應的UID。

1. 獲得用戶專欄入口:

URI: http://www.zhihu.com/people/{{%UID}}/posts GET/HTTP 1.1
XPATH: //div[@id="zh-profile-list-container"]

解析上述內容,可獲得該用戶所有的專欄入口地址。

2. 獲得專欄文章信息:

URI: http://zhuanlan.zhihu.com/api/columns/{{%UID}}/posts?limit={{%LIMIT}}offset={{%OFFSET}} GET/HTTP 1.1

{{%LIMIT}}: 表示該次GET請求獲取數據項的數量,即專欄文章信息數量。我沒有具體測試過最大值為多少,但是可以設置為比默認值大。默認值為10。
{{%OFFSET}}: 表示該次GET請求獲取數據項的起始偏移。
解析上述內容,可以獲得每篇專欄文章的信息,比如標題、題圖、專欄文章摘要、發布時間、贊同數等。該請求返回JSON數據。
注意:解析該信息時,可以獲得該篇專欄文章的鏈接信息。

3. 獲得專欄文章:

URI: http://zhuanlan.zhihu.com/api/columns/{{%UID}}/posts/{{%SLUG}} GET/HTTP 1.1

{{%SLUG}}: 即為2中獲得的文章鏈接信息,目前為8位數字。
解析上述內容,可以獲得專欄文章的內容,以及一些文章的相關信息。該請求返回JSON數據。

上述這些應該足夠滿足題主的要求了。最重要的還是要善用Chrome調試工具,此乃神器!

* * * * * * * * * *
以下是一些零散的更新,用於記錄知乎爬蟲的想法。當然,相關實現還是要尊重ROBOTS協議,可以通過http://www.zhihu.com/robots.txt查看相關參數。

UID是對應該用戶所有信息的入口。

雖然用戶信息有修改間隔限制(通常為若干月不等),但考慮到即使是修改用戶名的操作也會使得UID變更,進而令先前的存儲失效。當然這也是可以突破的:用戶hash。這個hash值為32位字元串,對每個賬號是唯一且不變的。

通過UID獲得hash:

URI: http://www.zhihu.com/people/%{{UID}} GET/HTTP 1.1
XPATH: //body/div[@class="zg-wrap zu-main"]//div[@class="zm-profile-header-op-btns clearfix"]/button/@data-id

解析上述內容,可獲得UID對應的hash值。(沒錯,這個值就是存在「關注/取消關注」這個按鈕里的。)這樣即可唯一標識用戶。

目前還沒有找到方法通過hash_id獲得UID,但是有間接方法可以參考:通過關注列表定期檢查用戶信息是否變更,當然關注/取消關注操作也可以自動化:

關注操作
URI: http://www.zhihu.com/node/MemberFollowBaseV2 POST/HTTP 1.1
Form Data
method: follow_member
params: {"hash_id": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"}
_xsrf: &

取消關注操作
URI: http://www.zhihu.com/node/MemberFollowBaseV2 POST/HTTP 1.1
Form Data
method: unfollow_member
params: {"hash_id": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"}
_xsrf: &

知乎爬蟲需要一份UID列表才能正常運轉,如何獲得這份列表是一個需要考慮的問題。目前一個可行的想法是選定若干大V用戶,批量爬取其被關注列表。舉例來說,張公子目前被關注數達到58W+,通過:

URI: http://www.zhihu.com/node/ProfileFollowersListV2 POST/HTTP 1.1
Form Data
method: next
params: {"offset": {{%OFFSET}}, "order_by": "hash_id", "hash_id": "{{%HASHID}}"}
_xsrf: &

每次可以獲得20條關注者的用戶信息。這些信息中包含hash_id、用戶名、UID、關注/被關注數、、提問數、回答數等。


這裡以個人專欄做一下示例獲取專欄所有文章信息和專欄相關信息(包含代碼)
第一步:專欄地址:https://zhuanlan.zhihu.com/passer
後面的passer是作為專欄的唯一標識。通過任何一工具抓包,得到url:
https://zhuanlan.zhihu.com/api/columns/passer
返回的json數據。
抓包出來的數據展示為(用瀏覽的f12抓包):

對json數據進行解析。
大致代碼如下(對於以下代碼,如果嫌麻煩可以自行把生成驗證碼部分的代碼刪除。請求多次之後會無效):

#encoding=utf8
import time
import requests
from bs4 import BeautifulSoup

Default_Header = {"X-Requested-With": "XMLHttpRequest",
"Referer": "http://www.zhihu.com",
"User-Agent": "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; "
"rv:39.0) Gecko/20100101 Firefox/39.0",
"Host": "www.zhihu.com"}
_session = requests.session()
_session.headers.update(Default_Header)

BASE_URL = "https://www.zhihu.com"
CAPTURE_URL = BASE_URL+"/captcha.gif?r="+str(int(time.time())*1000)+"type=login"
PHONE_LOGIN = BASE_URL + "/login/phone_num"

def login():
"""登錄知乎"""
username = ""#你自己的帳號密碼
password = ""
cap_content = _session.get(CAPTURE_URL).content
cap_file = open("/root/Desktop/cap.gif","wb")
cap_file.write(cap_content)
cap_file.close()
captcha = raw_input("capture:")
data = {"phone_num":username,"password":password,"captcha":captcha}
r = _session.post(PHONE_LOGIN, data)
print (r.json())["msg"]

def zhuanlan_info():
Default_Header = {
"Referer": "https://zhuanlan.zhihu.com/passer",
"User-Agent": "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; "
"rv:39.0) Gecko/20100101 Firefox/39.0",
"Host": "zhuanlan.zhihu.com"}
_session = requests.session()
_session.headers.update(Default_Header)
HtmlContent = _session.get("https://zhuanlan.zhihu.com/api/columns/passer")
HtmlContent = HtmlContent.json()
print "專欄名稱 :"+HtmlContent["name"].encode("utf-8")
print "專欄關注人數:"+str(HtmlContent["followersCount"])
print "專欄文章數量:"+str(HtmlContent["postsCount"])
print "專欄介紹 :"+HtmlContent["description"].encode("utf-8")
print "專欄創建者相關信息:"
print "1、地址::"+HtmlContent["creator"]["profileUrl"].encode("utf-8")
print "2、個簽::"+HtmlContent["creator"]["bio"].encode("utf-8")
print "3、昵稱::"+HtmlContent["creator"]["name"].encode("utf-8")
print "4、hash::"+HtmlContent["creator"]["hash"].encode("utf-8")
print "5、介紹::"+HtmlContent["creator"]["description"].encode("utf-8")

運行抓取的結果是:

專欄名稱 :學習編程
專欄關注人數:43387
專欄文章數量:39
專欄介紹 :莫道君行早,更有早行人。
全心敲代碼,天道自酬勤。
專欄創建者相關信息:
1、地址::https://www.zhihu.com/people/sgai
2、個簽::教你如何編程
3、昵稱::路人甲
4、hash::eaf435b228ce0b038a4afe8203f59b49
5、介紹::愛編程、跑步、旅遊、攝影
學習編程專欄:https://zhuanlan.zhihu.com/passer
已有女友,勿擾。

至此專欄信息抓取完畢。
第二步:抓文章信息。
https://zhuanlan.zhihu.com/api/columns/passer/posts?limit=20offset=35
limit後面的參數值表示:限制每次下拉刷新請求的文章數量,這裡無論你怎麼修改知乎限定了20,後面的offset值表示從哪一篇文章開始。如果是20表示從第二十20篇文章開始往後請求,請求更久的文章。

如果我們想抓取一個專欄的所有文章,我們不難想到,一次一次請求連接起來獲取到所有的文章列表以及內容。
比如這樣:
第一次:limit = 20 , offset = 0
第二次:limit = 20 , offset = 20
第三次:limit = 20 , offset = 40
...
如此可以看出每次設定起始位置+20即可,limit不變,這兩個參數進行請求,獲取json數據,對json數據進行解析。
大致代碼如下(再說一點關於帳號密碼為手機帳號密碼,此處如需郵箱帳號密碼登錄只需要改一下登錄地址和參數名稱請自行修改):

#encoding=utf8
import time
import requests
from bs4 import BeautifulSoup

Default_Header = {"X-Requested-With": "XMLHttpRequest",
"Referer": "http://www.zhihu.com",
"User-Agent": "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; "
"rv:39.0) Gecko/20100101 Firefox/39.0",
"Host": "www.zhihu.com"}
_session = requests.session()
_session.headers.update(Default_Header)

BASE_URL = "https://www.zhihu.com"
CAPTURE_URL = BASE_URL+"/captcha.gif?r="+str(int(time.time())*1000)+"type=login"
PHONE_LOGIN = BASE_URL + "/login/phone_num"
BASE_ZHUANLAN_API = "https://zhuanlan.zhihu.com/api/columns/"
BASE_ZHUANLAN = "https://zhuanlan.zhihu.com"

def login():
"""登錄知乎"""
username = ""#你得帳號密碼(此處為手機帳號)
password = ""
cap_content = _session.get(CAPTURE_URL).content
cap_file = open("cap.gif","wb")
cap_file.write(cap_content)
cap_file.close()
captcha = raw_input("capture:")
data = {"phone_num":username,"password":password,"captcha":captcha}
r = _session.post(PHONE_LOGIN, data)
print (r.json())["msg"]

def zhuanlan_text():
Default_Header = {
"Referer": "https://zhuanlan.zhihu.com/passer",
"User-Agent": "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; "
"rv:39.0) Gecko/20100101 Firefox/39.0",
"Host": "zhuanlan.zhihu.com"}
_session = requests.session()
_session.headers.update(Default_Header)
TextAPI = BASE_ZHUANLAN_API+"passer/posts?limit=20offset="
endFlag = True
offset = 0
while endFlag:
TextContentHTML = (_session.get(TextAPI+str(offset))).json()
for everText in TextContentHTML:
print "文章作者相關:"
print "1、地址::"+everText["author"]["profileUrl"].encode("utf-8")
print "2、個簽::"+everText["author"]["bio"].encode("utf-8")
print "3、昵稱::"+everText["author"]["name"].encode("utf-8")
print "4、hash::"+everText["author"]["hash"].encode("utf-8")
print "5、介紹::"+everText["author"]["description"].encode("utf-8")
print "文章標題 :"+everText["title"].encode("utf-8")
print "文章地址 :"+BASE_ZHUANLAN+everText["url"].encode("utf-8")
print "文章推送時間:"+everText["publishedTime"].encode("utf-8")
print "文章評論數量:"+str(everText["commentsCount"])
print "文章點贊數量:"+str(everText["likesCount"])
print "文章內容 :"+everText["content"].encode("utf-8")
if(len(TextContentHTML) &< 20): endFlag = False offset = offset + 20

運行的部分結果截圖:

以上~


抓ajax請求直接分析就是。和抓傳統頁面相比還省去數據提取的步驟。


以上答主的方法都挺好的。

然而,對於小白來說,有更加方便的方式獲取知乎專欄的信息。

上周在github瞎逛,偶然發現 @7sDream 大神寫的 zhihu_oauth 模塊,一口氣看完了源碼(然而並沒有看懂,尷尬)。
具體的信息可以查看 zhihu_oauth 模塊的文檔 : http://zhihu-oauth.readthedocs.io/zh_CN/latest/

下面進入教程模式:
一. 安裝 zhihu_oauth

&>&>&> pip install zhihu_oauth

二. 使用 load_token 登錄

TOKEN_FILE = "token.pkl"

client = ZhihuClient()

if os.path.isfile(TOKEN_FILE):
client.load_token(TOKEN_FILE)
else:
client.login_in_terminal()
client.save_token(TOKEN_FILE)

三. 獲取專欄信息

def getZhuanLan(self,client,zhuanlan):
zl = ZhuanLan()
column = client.column(zhuanlan)

# 專欄id
zl.set("name",column.id)
# 專欄作者
zl.set("author",column.author.name)
# 專欄名
zl.set("title",column.title)
# 專欄描述
zl.set("description",column.description)
# 專欄文章數量
zl.set("articles_count",column.articles_count)

四. 獲取文章信息

def getArticle(self,client,zhuanlan,zl):
column = client.column(zhuanlan)
relation = zl.relation("containedArticles")

for article in column.articles:
try:
art = Article()
# 文章標題
print(("title",article.title))
art.set("title",article.title).save()
# 作者
art.set("author",article.author.name).save()
# 文章摘要
art.set("excerpt",article.excerpt).save()
# 文章正文
soup = BeautifulSoup(article.content,"lxml")
content = soup.get_text()
art.set("content",content).save()
# 所屬專欄名
art.set("column_name",article.column.title).save()
# 文章id
art.set("art_art_id",article.id).save()
# 文章封面圖
art.set("image_url",article.image_url).save()
# 點贊數
art.set("voteup_count",article.voteup_count).save()
# 發表時間
localtime = time.localtime(article.updated_time)
created_time = time.strftime("%Y-%m-%d %H:%M:%S", localtime)
art.set("created_time",created_time).save()
relation.add(art)
print("正在保存第" + str(self.num) + "篇文章")
self.num += 1
time.sleep(2)
except:
pass

五. 數據存儲

class ZhuanLan(Object):
# name
@property
def name(self):
return self.get("name")
@name.setter
def name(self, value):
return self.set("name", value)

# author
@property
def author(self):
return self.get("author")
@author.setter
def author(self, value):
return self.set("author", value)

# title
@property
def title(self):
return self.get("title")
@title.setter
def title(self, value):
return self.set("title", value)

# description
@property
def description(self):
return self.get("description")
@description.setter
def description(self, value):
return self.set("description", value)

# articles_count
@property
def articles_count(self):
return self.get("articles_count")
@articles_count.setter
def articles_count(self, value):
return self.set("articles_count", value)

  • 數據的存儲為 leanCloud 雲存儲。

六. 數據截圖

專欄

文章

完整版代碼: https://github.com/RayYu03/ZhuanlanCrawler/blob/master/module%20method/start.py

以上~


第一次回答

寫過不少爬蟲,我覺得分析請求比代碼更重要,常用的抓包工具要掌握,比如httpwatch、Charles這些。得到具體的請求url之後,代碼就是如何偽裝成正常訪問的一個實現過程,藉助一些開源框架,或者自己從頭實現,都是可以的。

最近開發了一款資訊app,其中抓了一些比較優秀的專欄內容,知乎專欄是直接非同步請求返回的json數據,解析起來簡直爽的不要不要的。


大家用爬蟲都是解析網頁嗎?好吧,我是直接解析了客戶端的API,然後模擬客戶端發起連接開始爬。。。


推薦閱讀:

TAG:Python | 爬蟲計算機網路 | AngularJS | 知乎專欄 |