圍觀知乎真福利話題,放鬆一下。
前言:
週末了,圍觀幾個知乎福利話題:
女生身材好是什麼體驗?:https://www.zhihu.com/question/328457531
擁有一雙大長腿是怎樣的體驗?:https://www.zhihu.com/question/297715922
有個身材火辣的女朋友是怎樣一種體驗?:https://www.zhihu.com/question/313825759
這是真福利吖,點開一個問題看一下答主的回答,,,全都是高質量圖片:
圖片都是一些個人精品照,質量很高。
可是這每個話題下都有上千條回答,這得看到啥時候,不停的重新整理也得很久。。。於是就寫了這段程式碼把這些圖片都下載了,考慮到一些資料可能用到,就順道一起存到資料庫了。包括圖片地址、答主主頁地址、答主暱稱、答主、個性簽名、答主粉絲、相關問題地址、贊同數等等等。看成果圖:
我懷疑你在開車,但是我沒有證據
尊重答主的分享,所以 以每一位答主暱稱來命名其圖片的父目錄資料夾。但是最後瀏覽圖片一個一個資料夾太麻煩了,所以我統一整理了一下一共 三個話題下2325張圖片放在一個資料夾裡,想直接觀摩一下的同學公眾號後臺回覆:知乎,壓縮包合集發給你(一個月內有效),下面是講獲取的方法,只對圖片感興趣的不用看了,去回覆吧。
言歸正傳:
點開一個話題,進入開發者工具,重新整理頁面,在xhr欄目下,會發現很多請求,左上角過濾一下,只有以 answers?
開頭的才是回答內容,分析一下請求頭:
知乎的請求結構出奇的簡單,很意外。關鍵資訊已在圖片標明。基礎URL是:https://www.zhihu.com/api/v4/questions/313825759/answers?include=
Query String Parameters
構造出來的。
然後我們看一下答主的回答內容:
這裡回答內容有可能是引用其他話題 擁有一雙大長腿是怎樣的體驗?
的圖片,也就是說,我們只要再把這個引用的話題地址獲取下來,再根據新獲取的地址構造請求URL, 得到該話題的請求地址,這樣就可以順著一條回答把所有引用的相似問題其他答主的圖片全部下載下來。。。
點選引用的其他話題,切換知乎話題 擁有一雙大長腿是怎樣的體驗?
,我們再看一下回答內容:
同樣看一下該話題的 Query String Parameters
只有 limit 、offset兩個屬性會變,而limit 為限制當頁顯示的回答數量,offset 為偏移量,就是本頁從第幾條回答開始顯示,其他屬性全是相同的(知乎頁面限制顯示回答數最大20)。這意味著不管知乎哪個問題都可由該問題的地址以相同的方法構造請求URL:
param = {
'include': '',#太長了,不展示了
'limit': '20', # 限制當頁顯示的回答數,知乎最大20
'offset': offset, # 偏移量
'platform': 'desktop',
'sort_by': 'default',
}
base_URL = 'https://www.zhihu.com/api/v4/questions/297715922/answers?include=' # 基礎 url 用來構造請求url
url = base_URL + urllib.parse.urlencode(param) # 構造請求地址
再點選 preview 看返回的 json 格式的資訊:
有個totals,是該話題下總回答數,可以根據這個計算多少次可以遍歷全部回答,考慮到後面回答內容質量就跟不上了,我們只獲取前800條回答。
展開一條回答:
所有的資訊包括答主資訊和回答的資訊都在了,content內容就是回答內容,複製下來,格式化發現這是css渲染的內容,也能理解,知乎回答必須要用富文字方式編輯,返回的內容必然是這種格式。看一下回答內容:
這個層次很明瞭,a 節點的 href 屬性就是引用的相關問題的地址。figure 節點 下 noscript 節點下 img節點的 src 屬性就是圖片地址。用 pyquery 解析:
for answer in json['data']:
answer_info = {}
# 獲取作者資訊
author_info = answer['author']
author = {}
author['follower_count'] = author_info['follower_count'] # 作者被關注數量
author['headline'] = author_info['headline'] # 個性簽名
author['name'] = author_info['name'] # 暱稱
author['index_url'] = author_info['url'] # 主頁地址
# 獲取回答資訊
voteup_count = answer['voteup_count'] # 贊同數
comment_count = answer['comment_count'] # 評論數
# 解析回答內容
content = pq(answer['content']) # content 內容為 xml 格式的網頁,用pyquery解析
imgs_url = []
imgs = content('figure noscript img').items()
for img_url in imgs:
imgs_url.append(img_url.attr('src')) # 獲取每個圖片地址
# 獲取回答內容引用的其他相似問題
question_info = content('a').items()
......太多不全部展示了,有興趣可以看一下文末完整原始碼
飲水思源儲存檔案以答主暱稱命名,以示尊敬:
def save_to_img(imgs_url, author_name, base_path):
path = base_path + author_name
if not os.path.exists(path): # 判斷路徑資料夾是否已存在
os.mkdir(path)
for url in imgs_url:
try:
response = requests.get(url)
if response.status_code == 200:
img_path = '{0}/{1}.{2}'.format(path,
md5(response.content).hexdigest(), 'jpg') # 以圖片的md5字串命名防止重複圖片
if not os.path.exists(img_path):
with open(img_path, 'wb') as jpg:
jpg.write(response.content)
else:
print('圖片已存在,跳過該圖片')
except requests.ConnectionError:
print('圖片連結失效,下載失敗,跳過該圖片')
print('已儲存答主:' + author_name + ' 回答內容的所有圖片')
以圖片內容的 md5 編碼命名可以防止重複圖片,如果圖片被其他人下載之後加水印再上傳,圖片內容是不同的,所以可能有重複照片。
如果有需要可以把這些資料存到資料庫,這裡我以mongoDB為例:
#儲存在mongoDB
client = MongoClient(host='localhost')
print(client)
db = client['zhihu']
collection = db['zhihu']
def save_to_mongodb(answer_info):
if collection.insert(answer_info):
print('已儲存一條回答到MongoDB')
圖中儲存了答主引用的其他話題標題及地址,可以把這個地址傳回去迴圈獲取,直到所有類似話題圖片全部下載。
算了,太多了,營養跟不上。這裡就打包開頭那三個話題前800條回答的圖片共2325張。公眾號回覆 知乎 獲得壓縮包。
結語:
後面我大概看了一下里面的圖片,裡面還是有一點點重複的,而且還有一些什麼表情圖在裡面,這都沒什麼,忍不了的是裡面還有一點男士 秀自己的照片。。。跟預期不一樣吖。
大家可根據情況加些判斷函式,例如圖片中間大概位置的畫素點是否相同,來真正的把重複圖片去掉。加些人體身材特徵值對比,去掉男士的圖片和表情圖。這個太慢了,有時間的朋友自行發揮,我是要出去玩嘍,週末開心。
原始碼地址:https://github.com/zhangzhe532/icodebugs/tree/master/DataAnalysis/zhihu_get_pic
公眾號:愛寫bug