上篇文章說目標是想拉一丟知乎的用戶數(shù)據(jù) 這周正好有時間 給他整一發(fā)大的
-
目標
1 通過登錄賬號獲取關注用戶(第一層)
2 通過獲取到的關注用 繼續(xù)循環(huán)獲取關注用戶 level++
3 用戶信息表格截個圖 大概就是這些
-
實現(xiàn)方式
1 語言 Python3
2 庫--requests gevent BeautifulSoup(lxml)
3 數(shù)據(jù)庫 MongoDB
4 PIL(圖像庫) pytesseract(驗證碼識別-識別率低(不是重點))
-
整體過程實現(xiàn)邏輯
1 登陸賬號 以當前賬號為中心用戶 獲取基本信息 保存數(shù)據(jù)庫 默認完成狀態(tài)是false
2 循環(huán)開啟 每次讀取最早的一條 status=False的用戶 異步 讀取關聯(lián)url數(shù)據(jù)存儲 依次循環(huán)
-
現(xiàn)在一步一步做
整個代碼結構圖
本來還想開著多進程 后面試了一下 沒幾下知乎就給我限制ip了
發(fā)現(xiàn)當前這種 算是沒有在沒有代理ip的情況下速度還算不錯的
后面有爬取一個代理ip網(wǎng)站 但是用起來多數(shù)不可用或者說可用時間太短(正式因為被限制ip了 和 mac總是黑屏后 自動斷網(wǎng) 就算了 目的達到了 也抓了一丟 ~)
-###代碼過程
1 登陸 要從當前用戶開始 先得登陸 保持登陸狀態(tài) 就是保持cookie狀態(tài) cookie作為服務器識別你是否登陸的標志
所以說到底我們就要在登陸這個操作我們模擬完成后 存cookie 后續(xù)的鏈接訪問后帶上這個cookie 這里我們用的requests 特別方便 直接session-requests就能共享上個連接的cookie
登陸中 有可能遇到驗證碼 這里用到了自動識別三次 失敗就會彈出圖片手動輸入
-
getMyCenterURL 判斷登陸成功與否 隨便用登陸成功后能娶到的一個標識來判斷就行
'''
if name == 'main':
account = 'phone or email'
secret = 'XXOO'
refer="http://www.zhihu.com/"
if(getMyCenterURL()):
#登陸狀態(tài)
pass
else:
LoginActon.login(secret,account,session,headers)
getMyCenterURL()'''
-
登陸沒成功說明cookie失效 這里通過抓取到的接口來登陸 至于怎么抓取 網(wǎng)上很多講解的 主要步驟
1 chrome F12 前往知乎登陸頁面 故意輸錯密碼 抓取登陸接口 與 request from表單 看post的內容 這個網(wǎng)上很多
2 驗證碼 同意的抓取 這里用了自動是別的一個庫 不過成功率 太低 三次自動識別失敗 就彈出圖片手動輸入
3 cookie保存
cookie載入 名為cookies
'''
session = requests.session()
session.cookies = cookielib.LWPCookieJar(filename='cookies')
try:
session.cookies.load(ignore_discard=True)
except:
print("Cookie 未能加載")
'''
登陸之后 session.cookies.save()保存
4 登陸成功后 就可以開始抓取數(shù)據(jù)了 先把自己作為第一個用戶抓取保存數(shù)據(jù)庫 后面就以自己為中心開始抓取
login ok 后的數(shù)據(jù)抓取調用 -
數(shù)據(jù)抓取 DataParseAction類 看看整體代碼
兩個方法
1 根據(jù)用戶關注用戶url獲取關注列表用戶
2 根據(jù)用戶主頁 抓取用戶基本新保存數(shù)據(jù)庫
方法一 在while 循環(huán)中一條一條的從數(shù)據(jù)庫取出來 塞入方法2中 方法2保存具體數(shù)據(jù)具體代碼
import random
import time
import gevent
import pymongo
import requests
__author__ = 'Daemon1993'
from bs4 import BeautifulSoup, SoupStrainer
from ZHSpider import LoginActon
from ZHSpider import getProxyIP
'''
解析 用戶主頁數(shù)據(jù)
parmas url userHomePage
get all attention users
'''
# 全局 count 每次解析一個 就加一
count = 0
sleep_time = 0
sleep_timeCount = 0
# tagName_ClassName 獲取相關數(shù)據(jù)
def getTagTextByName_Class(soup, tagName, class_name, data, key):
try:
value = soup.find(tagName, class_=class_name).text
data[key] = value
except Exception as e:
pass
# getTitle by Name_Class
def getTagTitleByName_Class(soup, tagName, class_name, data, key):
try:
value = soup.find(tagName, class_=class_name).get('title')
data[key] = value
except:
pass
def getSexByName_Class(soup, tagName, class_name, data, key):
try:
data[key] = "未知"
value = soup.find(tagName, class_=class_name)
value = value.find('i')
tags = value.get('class')
tag_str = "".join(tags)
if (tag_str.find('female') != -1):
data[key] = "female"
else:
data[key] = "male"
except:
pass
# 獲取關注詳情
def getFollowsDetail(soup, tag1, class1, tag2, class2, data, attr_name, key):
try:
data[key] = LoginActon.index_url + soup.find(tag1, class_=class1).find(tag2, class_=class2).get(attr_name)
# 獲取 關注信息
index = 0
for tag in soup.find(tag1, class_=class1).find_all("strong"):
if (index == 0):
data["followees"] = tag.text
else:
data["followers"] = tag.text
index += 1
except:
return False
pass
def getAttentionContent(soup, data):
try:
topics = []
for img in soup.find("div", class_="zm-profile-side-topics").find_all("img"):
topics.append(img.get('alt'))
data["topics"] = topics
except:
pass
def changeRefer(headers,refer):
headers["Referer"]=refer
# 根據(jù)URL獲取 數(shù)據(jù) 解析保存
def saveDataByUrl(from_url, url, headers, zh, relation_level):
data = {}
data['_id'] = url
data['from_url'] = from_url
global sleep_time
if(sleep_time!=0):
sleep_time=random.randint(0,5)
if(sleep_time>3):
print('sleep {0}'.format(sleep_time))
time.sleep(sleep_time)
r = requests.session()
try:
if(from_url!=url):
changeRefer(headers,from_url)
html = r.get(url,timeout=5.0, headers=headers)
except Exception as e:
print("saveDataByUrl {0}".format(e))
return
pass
only_data_info = SoupStrainer("div", class_="zm-profile-header-main")
soup_info = BeautifulSoup(html.text, "lxml", parse_only=only_data_info)
getTagTextByName_Class(soup_info, "span", "name", data, "name")
getTagTitleByName_Class(soup_info, "div", "bio ellipsis", data, "introduction")
getTagTitleByName_Class(soup_info, "span", "location item", data, "location")
getTagTitleByName_Class(soup_info, "span", "business item", data, "business")
getSexByName_Class(soup_info, "span", "item gender", data, "gender")
getTagTitleByName_Class(soup_info, "span", "employment item", data, "work_adr")
getTagTitleByName_Class(soup_info, "span", "position item", data, "work_direction")
getTagTitleByName_Class(soup_info, "span", "education item", data, "education_school")
getTagTitleByName_Class(soup_info, "span", "education-extra item", data, "education_direction")
try:
description = soup_info.select('span[class="description unfold-item"] span[class="content"]')[0].get_text()
data["description"] = description
except Exception as e:
pass
# 關注行為
only_data_action = SoupStrainer("div", class_="zu-main-sidebar")
soup_action = BeautifulSoup(html.text, "lxml", parse_only=only_data_action)
getFollowsDetail(soup_action,
"div", "zm-profile-side-following zg-clear",
"a", "item",
data, "href", "followees_url")
# 獲取關注話題
getAttentionContent(soup_action, data)
global count
try:
data["relation_level"] = relation_level
# 當前賬號 的關注賬號 默認沒有被全部加載
data["followees_status"] = False
if(count%50==0):
print(data)
#200一次隨機大于 不停 小于停
if(count%200==0):
if(sleep_time>3):
sleep_time=0
else:
sleep_time=random.randint(0,5)
zh.insert(data)
except:
pass
count += 1
return True
def startSpider(session, headers, zh):
print('知乎爬蟲 開始工作 ------ 飛起來唧龄。。廓鞠。匙奴。')
# 獲取當前DBzhong status=False的所有URL 最大5000
while True:
tasks = []
userinfo= zh.find_one({"followees_status": False})
if(userinfo is None):
break
from_url = userinfo['_id']
try:
followees_url = userinfo['followees_url']
except:
zh.remove(from_url)
print('delete {0} '.format(from_url))
continue
pass
relation_level = userinfo['relation_level']
tasks.append(gevent.spawn(getAllAtentionUsers,
from_url, followees_url, session, headers, zh, relation_level + 1,userinfo))
gevent.joinall(tasks)
# 獲取當前所有的關注用戶列表 返回
def getAllAtentionUsers(from_url, follows_url, session, headers, zh, relation_level,userinfo):
if (follows_url is None):
return
html = ""
r = requests.session()
r.cookies = session.cookies
try:
changeRefer(headers,from_url)
html = r.get(follows_url, timeout=5.0, headers=headers).text
except Exception as e:
pass
relation_info = SoupStrainer("div", class_="zm-profile-section-wrap zm-profile-followee-page")
soup = BeautifulSoup(html, "lxml", parse_only=relation_info)
urls = []
for user in soup.find_all("div", class_="zm-profile-card zm-profile-section-item zg-clear no-hovercard"):
user_a = user.find("a")
url = LoginActon.index_url + user_a.get('href')
urls.append(url)
# 保存每個關注的用戶信息
print('user {0} follows size{1} '.format(from_url, len(urls)))
tasks = [gevent.spawn(saveDataByUrl, from_url, url, headers, zh, relation_level) for url in urls]
gevent.joinall(tasks)
try:
userinfo["followees_status"]=True
zh.save(userinfo)
except:
pass
print('用戶 {0} followees save OK save count {1}'.format(from_url, count))
-
遇到狀況
周六晚上代碼寫完后 掛著跑了一晚上 不知道為啥電腦熄屏后 網(wǎng)絡斷了 獲取了7000多條 relation_level 到了4
還特意設置永不睡眠 周天白天重新開始 ??
然后知乎給我限制ip啦
也不知道為啥 電腦能訪問 請求返回 提示 ip次數(shù)過多 然后今天一天都在研究怎么繞開歸根打的就是 不要只用一個ip 你可以多臺機器爬去 可以每次撥號動態(tài)分配ip 什么的
后面我也去扒了一些免費代理ip網(wǎng)站的ip
用起來也不是順利 大部分不能用 明明驗證百度能過 訪問知乎就readTimeout 這里整了幾個小時后 后面 用一個list存ip
從本機開始 如果失敗 就取新的ip刪除list中舊的 但是一運行 大部分超時 能用的也很慢 (不太理想)后面還是放棄了 周一上班去 電腦掛著 回來又斷網(wǎng)了 想想先這樣吧
- 項目要跑起來
1 用戶名密碼輸入
2 有mongoDB數(shù)據(jù)庫 本地安裝也行
github地址