1. 程式人生 > >python requests 爬取知乎使用者資訊

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+,分析不能代表大範圍,僅參考練習

性別分佈


另外一篇正方系統爬取練習 點選開啟連結