zoukankan      html  css  js  c++  java
  • python知乎内容抓取(话题结构、关注人数、问题数、精华问题等相关内容,redis存储)

      因为平时喜欢上知乎,发现其话题是一个有向无环图(自己介绍说得),一级一级往上最后到根话题,所以我就想尝试从根话题一级一级往下将其全部内容爬取。最后实践过程中发现自己想多了..有以下三个问题:

      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()

    运行结果:

  • 相关阅读:
    redis 高级
    redis连接
    redis脚本
    Redis事务
    redis发布订阅
    加一
    C语言从代码中加载动态链接库
    GCC编译器
    Vim编辑器基础
    Linux用户的创建与授权及修改密码
  • 原文地址:https://www.cnblogs.com/prophet-ss/p/9416129.html
Copyright © 2011-2022 走看看