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可视化分析(具体用法百度)
由于爬的用户是来自用户关注列表,所以都是粉丝数比较多的用户,用户数据只有4000+,分析不能代表大范围,仅参考练习
性别分布