python requests 爬取知乎用户信息

今天尝试了爬取知乎用户信息来练习爬虫,学到了很多东西,在这里总结一下心得

我没有使用爬虫框架,就只用了requests模块,应为爬取的都是json数据,连BeautifulSoup都没能用上

爬取知乎用户信息,可以不用模拟登录也能获取用户信息,只有一些设置了隐私才需要登录,我们这里不登录也能满足需求了

1.首先我们可以从一位知乎用户开始,先爬取他的关注列表的用户url_token

2.递归爬取他关注列表用户的关注列表,并存储在文本里

3.根据文本里的用户url_token一一爬取用户信息

4.写入数据库

5.搞一个代理ip池

import requests
import json
import pymysql

num=0        #设定爬取次数
user_all=[]  #存放本次运行的用户

首先先用requests写一个解析网页的函数

添加

def get_url(url):          #获取链接内容
    header_info = {
        "User-Agent": 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36',
    }
    user_url =url
    response =requests.get(user_url, headers=header_info)
    data = response.content
    data = data.decode('utf-8')           #设置字符集
    return data

然后我们需要获取用户的关注类别,我们可以从解析下面的api链接开始,右键检查network可以找到


https://www.zhihu.com/api/v4/members/excited-vczh/followees?include=data%5B*%5D.answer_count%2Carticles_count%2Cgender%2Cfollower_count%2Cis_followed%2Cis_following%2Cbadge%5B%3F(type%3Dbest_answerer)%5D.topics&offset=0&limit=20

其中的变量为用户的url_token,offest页数,limit显示的条数

我们从轮子哥的关注列表开始爬取

右键检查network,可以看到返回的是json数据,我们只需要里面的url_token


开始写解析函数

def get_follower(userID):             #解析内容,获取关注用户
    list=[]
    url = 'https://www.zhihu.com/api/v4/members/'+userID+'/followees?' \
          'include=data%5B*%5D.answer_count%2Carticles_count%2Cgender%2Cfollower_count%' \
          '2Cis_followed%2Cis_following%2Cbadge%5B%3F(type%3Dbest_answerer)%5D.topics&offset=0&limit=20'
    data = get_url(url)
    data = json.loads(data)
    print(data)
    for user in data:
        list.append(user['url_token'])
    return list

其中我们需要构造用户变量,页数和显示条数不变,我们获取第一页的数据就可以了。json数据我们可以使用python的数组来解析,使用json模块的json.loads()转为数组

递归爬取用户url_token

def digui(list):
        global num                   #全局变量,爬取多少次
        temporary = []               #存放本次爬取的用户名
        for url in list:
            if (num == 10):
                return 0
            else:
                num = num + 1
                print(num)
                list = get_follower(url)
                user_all.extend(list)             #全局变量,存放所有爬取的用户名
                temporary.extend(list)           #存放本次爬取的用户名
                print(list)
        digui(temporary)                         #递归爬取

首先我们需要设置一个全局变量来存储本次运行所爬取的url_token,temporary存储此次递归的url_token,然后以temporary为参数继续递归

爬取结果如下


获取了关注列表后,我们开始逐一获取用户信息,同样的方法我们通过以下api来获取

https://www.zhihu.com/api/v4/members/excited_vczh?include=locations%2Cemployments%2Cgender%2Ceducations%2Cbusiness%2Cvoteup_count%2Cthanked_Count%2Cfollower_count%2Cfollowing_count%2Ccover_url%2Cfollowing_topic_count%2Cfollowing_question_count%2Cfollowing_favlists_count%2Cfollowing_columns_count%2Cavatar_hue%2Canswer_count%2Carticles_count%2Cpins_count%2Cquestion_count%2Ccolumns_count%2Ccommercial_question_count%2Cfavorite_count%2Cfavorited_count%2Clogs_count%2Cmarked_answers_count%2Cmarked_answers_text%2Cmessage_thread_token%2Caccount_status%2Cis_active%2Cis_bind_phone%2Cis_force_renamed%2Cis_bind_sina%2Cis_privacy_protected%2Csina_weibo_url%2Csina_weibo_name%2Cshow_sina_weibo%2Cis_blocking%2Cis_blocked%2Cis_following%2Cis_followed%2Cmutual_followees_count%2Cvote_to_count%2Cvote_from_count%2Cthank_to_count%2Cthank_from_count%2Cthanked_count%2Cdescription%2Chosted_live_count%2Cparticipated_live_count%2Callow_message%2Cindustry_category%2Corg_name%2Corg_homepage%2Cbadge%5B%3F(type%3Dbest_answerer)%5D.topics

用户信息基本上就是在这里啦,接下来我们只需要解析json数据就可以了

def get_userInfo(userID):
    info=[]
    url="https://www.zhihu.com/api/v4/members/"+userID+"?include=locations%2Cemployments%2Cgender%2Ceducations%2Cbusiness%2Cvoteup_count%2Cthanked_Count%2Cfollower_count%2Cfollowing_count%2Ccover_url%2Cfollowing_topic_count%2Cfollowing_question_count%2Cfollowing_favlists_count%2Cfollowing_columns_count%2Cavatar_hue%2Canswer_count%2Carticles_count%2Cpins_count%2Cquestion_count%2Ccolumns_count%2Ccommercial_question_count%2Cfavorite_count%2Cfavorited_count%2Clogs_count%2Cmarked_answers_count%2Cmarked_answers_text%2Cmessage_thread_token%2Caccount_status%2Cis_active%2Cis_bind_phone%2Cis_force_renamed%2Cis_bind_sina%2Cis_privacy_protected%2Csina_weibo_url%2Csina_weibo_name%2Cshow_sina_weibo%2Cis_blocking%2Cis_blocked%2Cis_following%2Cis_followed%2Cmutual_followees_count%2Cvote_to_count%2Cvote_from_count%2Cthank_to_count%2Cthank_from_count%2Cthanked_count%2Cdescription%2Chosted_live_count%2Cparticipated_live_count%2Callow_message%2Cindustry_category%2Corg_name%2Corg_homepage%2Cbadge%5B%3F(type%3Dbest_answerer)%5D.topics"
    data=get_url(url)
    data = json.loads(data)
    if 'avatar_url' in data:
        info.append(data['avatar_url'])       #头像
    else:
        info.append('')
    if 'url_token' in data:
        info.append(data['url_token'])  # id
    else:
        info.append('')
    if 'name' in data:
        info.append(data['name'])
    else:
        info.append('')
    if 'gender' in data:
        info.append(data['gender'])  # 性别
    else:
        info.append('')
    try:
        if 'name' in data['locations'][0]:
            info.append(data['locations'][0]['name'])  # 居住地
        else:
            info.append('')
    except:
        info.append('')

    if 'business' in data:
        info.append(data['business']['name'])  # 所在行业
    else:
        info.append('')
    try:
        if "school" in data['educations'][0]:
            info.append(data['educations'][0]['school']['name'])  # 学校
        else:
            info.append('')
    except:
        info.append('')
    try:
        if 'major' in data['educations'][0]:
            info.append(data['educations'][0]['major']['name'])      #专业
        else:
            info.append('')
    except:
        info.append('')
    if 'follower_count' in data:
        info.append(data['follower_count'])  # 粉丝
    else:
        info.append('')
    if 'following_count' in data:
        info.append(data['following_count'])  # 关注
    else:
        info.append('')
    if 'voteup_count' in data:
        info.append(data['voteup_count'])  # 获赞
    else:
        info.append('')
    if 'thanked_count' in data:
        info.append(data['thanked_count'])  # 感谢
    else:
        info.append('')
    if 'favorited_count' in data:
        info.append(data['favorited_count'])  # 收藏
    else:
        info.append('')
    if 'answer_count' in data:
        info.append(data['answer_count'])  # 回答数
    else:
        info.append('')
    if 'following_question_count' in data:
        info.append(data['following_question_count'])  # 关注的问题
    else:
        info.append('')
    try:
        if 'company' in data['employments'][0]:
            info.append(data['employments'][0]["company"]['name'])  # 公司
        else:
            info.append('')
    except:
        info.append('')

    try:
        if 'job' in data['employments'][0]:
            info.append(data['employments'][0]["job"]['name'])  # 职位
        else:
            info.append('')
    except:
        info.append('')

    return info

因为有些用户是填写一些信息的,所以我们应该先判断json中是否有我们所需要的数据,如果没有,就等于空字符,有些数组不存时在会报错,我们可以使用try:....except:  来解决异常问题

获取了数据后就写入数据库,写入数据库我们用pymysql,没有这个模块的用户用pip安装。

创建表


其中设url_token表段唯一,这样在写入的时候就不会出现重复的用户了

def write_sql_info(list):                #用户信息写入数据库
    db = pymysql.connect("localhost","root","123456","zhihu_user",charset='utf8')
    # 使用cursor()方法获取操作游标
    cursor = db.cursor()
    # SQL 插入语句
    sql = """INSERT INTO user_info(avatar_url,url_token,name,gender,locations,business,school,major,follower_count,
following_count,voteup_count,thanked_count,favorited_count,answer_count,following_question_count,company,job)
                         VALUES ('""" + list[0] + """','""" + list[1] + """','""" + list[2] + """','""" + list[3] + """',
                         '""" + list[4] + """','""" + list[5] + """','""" + list[6] + """','""" + list[7] + """',
                         '""" + list[8] + """','""" + list[9] + """','""" + list[10] + """','""" + list[11] + """',
                         '""" + list[12] + """','""" +list[13] + """','""" + list[14] + """','""" + list[15] + """','""" + list[16] + """ ')"""
    try:
        # 执行sql语句
        print('写入用户成功')
        cursor.execute(sql)
        # 提交到数据库执行
        db.commit()
    except:
        print("已存在")
        # 如果发生错误则回滚info
        db.rollback()
        # 关闭数据库连接

    db.close()

主函数

if __name__ == '__main__':

    with open('./url.txt', 'r') as f:
        lines = f.readlines()  # 读取所有行
        last_line = lines[-1]  # 取最后一行
    user_id = last_line  # 继续上一次的爬取
    user = get_follower(user_id)
    if (user == None):
        print("没有关注的人")
    else:
        digui(user)
    user_all = list(set(user_all))  # 去掉重复的用户重
    f = open('./url.txt', 'a')  # 写入文本文件
    for text in user_all:
        f.write('\n' + text)
    user_list = user_all
    f.close()
    for id in user_list:      
        user_id = id
        info = get_user_info(user_id)
        info = [str(i) for i in info]  # 转为字符串
        print(info)
        write_sql_info(info)  # 写入数据库

主函数中我们使用一个文本文件来存放用户的url_token,每次运行时读取最后一行,这样就能从上次爬取的地方继续爬取

运行结果



当我们爬到一定程度时,会发现返回错误


这就要考虑一个问题了,程序的运行速度是很快的,如果我们利用一个爬虫程序在网站爬取东西,一个固定IP的访问频率就会很高,这不符合人为操作的标准,因为人操作不可能在几ms内,进行如此频繁的访问。所以一些网站会设置一个IP访问频率的阈值,如果一个IP访问频率超过这个阈值,说明这个不是人在访问,而是一个爬虫程序。

我的解决办法是弄一个代理ip池

如何建立一个爬虫代理ip池         

1、找到一个免费的ip代理网站(我这里用的是https://www.kuaidaili.com/free/)
2、爬取ip
3、检测ip可用性,移除不可用ip
4、随机取ip使用

新建ip.py文件

使用requests和BeautifulSoup爬取网站的ip

from bs4 import BeautifulSoup
import requests
import random
import urllib
ip_list=[]
def get_ip_list(url):
    headers = {
        'User-Agent': "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36"}
    for i in range(10,30):           #设置页数,这里设置10到30页
        i=str(i)                     
        url_c=url+'/inha/'+i+'/'     
        web_data = requests.get(url,headers=headers)
        soup = BeautifulSoup(web_data.text, 'html.parser')
        ips = soup.find_all('tr')
        for i in range(1, len(ips)):
            ip_info = ips[i]
            tds = ip_info.find_all('td')
            ip_list.append(tds[0].text+':'+tds[1].text)       #ip加端口号
        #检测ip可用性,移除不可用ip
    for ip in ip_list:                
        try:
          proxy_host = "https://" + ip
          proxy_temp = {"https": proxy_host}            
          res = urllib.urlopen(url, proxies=proxy_temp).read()           #访问一个网站,看看是否返回200
        except Exception as e:
          ip_list.remove(ip)                  #去除无效的ip
          continue
    return ip_list        

def get_random_ip(ip_list):          #在ip池中随机取一个ip使用
    proxy_list = []
    for ip in ip_list:
        proxy_list.append('http://' + ip)
    proxy_ip = random.choice(proxy_list)
    proxies = {'http': proxy_ip}
    return proxies
 
 
if __name__ == '__main__':
    url = 'https://www.kuaidaili.com/free/'
    ip_list = get_ip_list(url)
    proxies = get_random_ip(ip_list)
    print(ip_list)
    print(proxies)
爬取结果


request使用代理ip用proxies参数

response =requests.get(user_url, headers=header_info,proxies=proxies)

导入ip.py,然后使用我们爬到的ip作为参数写进去就好啦

每轮爬取时会使用不同的ip,这样ip被封的概率就减小了

使用pyecharts可视化分析(具体用法百度)

由于爬的用户是来自用户关注列表,所以都是粉丝数比较多的用户,用户数据只有3000+,分析不能代表大范围,仅参考练习

性别分布



另外一篇正方系统爬取练习  点击打开链接

猜你喜欢

转载自blog.csdn.net/wlustre/article/details/80709375