1. 程式人生 > >python知乎內容抓取(redis存儲)

python知乎內容抓取(redis存儲)

sorted mat param 爬取 Nid odi 請求 quest 一個

  因為平時喜歡上知乎,發現其話題是一個有向無環圖(自己介紹說得),一級一級往上最後到根話題,所以我就想嘗試從根話題一級一級往下將其全部內容爬取。最後實踐過程中發現自己想多了..有以下三個問題:

  1.數據量巨大,單臺電腦能力肯定不夠。我這裏只抓取了話題結構和話題對應的單個頁面(不翻頁)的一些呈現信息,沒有抓取具體問題回答的內容。

  2.如果直接連續抓取,沒多少次就會被知乎檢測到異常,這裏使用代理ip可以解決。

  3.最麻煩的是要查看所有話題結構必須要登錄(如下圖示例顯示的子話題是可以抓到的,但是點擊查看全部話題結構就需要登錄),一旦登錄就不可能連續抓取,除非能搞到很多賬號,而且還需要破解驗證等問題。我這塊目前只是從根話題抓取直接顯示出來的子話題,所以抓取到的話題數量和實際差的很遠(只是練習參考..)。後面我想了一個其他模式抓取,實際如果試驗可以會重新搞一套。

 技術分享圖片

  存儲由於key-value型很適合這裏,所以我使用的是redis數據庫,而且很快。一共有四張key-value表,zhTopicName保存話題名稱(key)-子話題id(value,後同),zhTopicTree保存話題id-[子話題id...],zhTopicMessage保存話題id-(關註人數,問題數),zhTopicQuestions保存話題id-[(問題名稱,作者,評論數,點贊數,鏈接)...]

參考代碼如下:

  1 # -*- coding: UTF-8 -*-
  2 
  3 from bs4 import BeautifulSoup
  4 import requests
5 import random 6 import redis 7 import time 8 import json 9 10 # 子話題目錄id獲取接口url,當前初始根話題id為19776749 11 CHILDREN_URL = https://www.zhihu.com/api/v3/topics/%u/children 12 13 # 知乎精華url 14 TOP_ANSWERS_URL = https://www.zhihu.com/topic/%u/top-answers 15 16 # 請求頭 17 HEADERS = {User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36
18 16 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36} 19 20 # 代理ip,這裏示例放了一個,自己可以在網上找些補充下 21 PROXIES_LIST = [ 22 {"http": "218.14.227.198:3124"}, 23 ] 24 25 pool = redis.ConnectionPool(host=localhost, port=6379, db=0) 26 27 r = redis.Redis(connection_pool=pool) 28 29 s = requests.session() 30 31 32 def crawl_message(uid): 33 try: 34 req = s.get(url=TOP_ANSWERS_URL % int(uid), headers=HEADERS, proxies=random.choice(PROXIES_LIST)).text 35 except Exception as e: 36 raise e 37 bf = BeautifulSoup(req, html.parser) 38 strong = bf.find_all(strong, class_=NumberBoard-itemValue) 39 # 0為關註人數,1為問題數 40 try: 41 r.hset(zhTopicMessage, uid, (strong[0].get(title), strong[1].get(title))) 42 except Exception as ex: 43 print(uid, ex) 44 return 45 items = bf.find_all(div, class_=ContentItem AnswerItem) 46 tqs = [] 47 for t in items: 48 # 問題、作者、評論數、點贊數、鏈接 49 tqs.append((eval(t.get(data-zop))[title], eval(t.get(data-zop))[authorName], 50 t.find(itemprop=commentCount).get(content), 51 t.find(button, class_="Button VoteButton VoteButton--up").contents[-1], 52 t.find(a).get(href))) 53 r.hset(zhTopicQuestions, uid, tqs) 54 time.sleep(0.1) 55 56 57 # 獲取話題的那個有向無環圖以及其名稱與id的對應關系並入庫 58 def crawl_topic(): 59 while len(crawl_topic.zhTopic) > 0: 60 try: 61 data = 62 json.loads( 63 s.get(url=CHILDREN_URL % int(crawl_topic.zhTopic[0]), headers=HEADERS, 64 proxies=random.choice(PROXIES_LIST)).text)[ 65 data] 66 except Exception as ex: 67 raise ex 68 if not data: # 已經到葉子話題返回 69 crawl_topic.zhTopic.pop(0) 70 continue 71 ids = [] 72 for m in data: 73 nid = m[id] 74 ids.append(nid) 75 crawl_topic.zhTopic.append(nid) 76 r.hset(zhTopicName, m[name], nid) 77 r.hset(zhTopicTree, nid, ids) 78 crawl_topic.zhTopic.pop(0) 79 80 81 def start_crawl(): 82 # 19776749為當前知乎根話題id 83 # 這裏構造一個id隊列避免使用遞歸 84 crawl_topic.zhTopic = [19776749] 85 r.hset(zhTopicName, 「根話題」, 19776749) 86 crawl_topic() 87 ids = r.hvals(zhTopicName) 88 for tid in ids: 89 crawl_message(tid) 90 91 92 def result_display(): 93 try: 94 for tnm in r.hkeys(zhTopicName): 95 tid = r.hget(zhTopicName, tnm) 96 print(話題:, tnm.decode(), 關註人數:, tid[1], 問題數:, tid[0]) 97 for e in eval(r.hget(zhTopicQuestions, tid).decode()): 98 print( , 問題:, e[0], 作者:, e[1], 評論數, e[2], 點贊數:, e[3], 鏈接:, e[4]) 99 except Exception as ex: 100 print(ex) 101 102 103 if __name__ == __main__: 104 start_crawl() 105 result_display()

  在我本機測試一共運行了5個小時左右獲取了1萬3千多個話題(如上問題3所述,遠少於實際話題數,估計不到十分之一),每個話題不到5條精華問題(頁面直接展示幾條就抓幾條,沒有翻頁)。下面展示下運行結果:

技術分享圖片

  最後按網上流行那種話題關註人數多少對應字體大小畫張圖展示下(我也是現學現賣,還是那句話僅供參考:))

代碼如下:

 1 import matplotlib.pyplot as plt
 2 from matplotlib import colors as mcolors
 3 import numpy as np
 4 import random
 5 import redis
 6 
 7 pool = redis.ConnectionPool(host=localhost, port=6379, db=0)
 8 
 9 r = redis.Redis(connection_pool=pool)
10 
11 followers = []
12 for tnm in r.hkeys(zhTopicName):
13     tid = r.hget(zhTopicName, tnm)
14     msg = r.hget(zhTopicMessage, tid)
15     if msg:
16         followers.append((tnm.decode(), int(eval(msg)[0])))
17 # 按關註人數取前300的話題
18 hotTopics = sorted(followers, key=lambda follower: follower[1], reverse=True)[0:300]
19 
20 plt.figure(figsize=(10, 6.18))
21 plt.setp(plt.gca(), frame_on=False, xticks=(), yticks=())
22 plt.axis([0, 100, 0, 100])
23 plt.rcParams[font.sans-serif] = [SimHei]
24 
25 randC = np.linspace(-20, 100, 300)
26 
27 colors = dict(mcolors.BASE_COLORS, **mcolors.CSS4_COLORS)
28 by_hsv = sorted((tuple(mcolors.rgb_to_hsv(mcolors.to_rgba(color)[:3])), name)
29                 for name, color in colors.items())
30 sorted_names = [name for hsv, name in by_hsv]
31 random.shuffle(sorted_names)
32 
33 random.shuffle(randC)
34 
35 rankings = 0
36 for topic in hotTopics:
37     plt.text(randC[rankings], randC[299 - rankings], topic[0], fontsize=(300 - rankings) / 10 + 10,
38              rotation=random.randint(-30, 30), style=italic, alpha=0.75,
39              color=sorted_names[rankings % len(sorted_names)])
40     rankings = rankings + 1
41 
42 plt.show()

運行結果:

技術分享圖片

python知乎內容抓取(redis存儲)