本文記錄了關于知乎用戶信息的模塊化抓取,使用到了Scrapy
這個開源項目,對其不熟悉的同學建議提前了解
知乎是現在十分活躍的社區,上面有關于人生、智慧、職業、技術等等的一系列的高質量的問答和專欄文章,雖然總是有有一些負面,片面的觀點,但是不得不承認這是一個積極的、開放的社區
而作為學習者,需要做的總是抱著開放的心態,去其糟粕而取之精華,知乎雖是虛擬,但更像現實。
起初我的思路是先使用Scrapy
模擬登錄上知乎,獲得server
分配的cookie
,借鑒了Github
上這個項目fuck-login,它的源碼使用的requests
模擬登陸知乎
requests session
在第一次登陸成功后,獲得cookie
設置之后,requests session
可以幫助我們管理cookie,這減少了很多的底層勞動。在下次爬取可以使用session
來進行get/post
請求
當然你也可以使用瀏覽器先登陸上知乎,直接將cookie
持久化到本地,之后再在請求的頭中帶上cookie
原理
模擬登陸
要將這個項目集成到Scrapy
當中,需要使用Scrapy
的Request
重寫下,實現模擬登錄
要想登陸上知乎,需要在請求頭中修改User-Agent
,并向login
頁面POST
一些數據,注意這里重寫了需要是用賬號為手機號登陸,至于email
登陸可以借鑒原項目
不知道要帶哪些東西就需要使用chrome開發者工具
先看下正常請求會帶哪些東西,之后再COSPLAY
,而fuck-login就幫助我們做好了這些事情
headers = {
"Host": "www.zhihu.com",
"Referer": "https://www.zhihu.com/",
'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:53.0) Gecko/20100101 Firefox/53.0'
}
postdata = {
'_xsrf': '',
'password': 'xxxxxx',
'phone_num': 'xxxxxx',
'captcha': ''
}
-
_xsrf
是藏在登陸頁面中的一串code
,所以在請求到首頁,需要在callback
函數中對這個值進行提取 -
phone_num
,password
就不用多說了,就是我們的登錄名和密碼 -
captcha
,這是驗證碼,我們需要在登錄頁面輸入的驗證碼
對于驗證碼的操作,也用很多種方式,有編碼實現,比如tesseract-ocr (google開源) 不推薦,也有在線打碼的平臺,準確率尚可,成本較低,比如云打碼
,還有其他人工打碼,準確率最高,但是成本也是最高
這里遵循fuck-login
的做法,將登陸頁面的驗證碼圖片鏈接下載到本地,直接手動輸入
源碼如下:
# -*- coding: utf-8 -*-
import scrapy
import re,os,json
import time
try:
from PIL import Image
except:
pass
class ZhihuSpider(scrapy.Spider):
name = "zhihu"
allowed_domains = ["www.zhihu.com"]
start_urls = ['https://www.zhihu.com/people/zhu-xiao-fei-47-24/following']
headers = {
"Host": "www.zhihu.com",
"Referer": "https://www.zhihu.com/",
'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:53.0) Gecko/20100101 Firefox/53.0'
}
postdata = {
'_xsrf': '',
'password': 'zhxfei..192',
'phone_num': '15852937839',
'captcha': ''
}
def parse(self, response):
with open('index.html','w') as f:
f.write(response.text)
print('over')
def start_requests(self):
return [scrapy.Request('https://www.zhihu.com/#login',
headers=self.headers,callback=self.get_xsrf)]
def get_xsrf(self,response):
response_text = response.text
mathch_obj = re.match('.*name="_xsrf" value="(.*?)"',response_text,re.DOTALL)
if mathch_obj:
self.postdata['_xsrf'] = mathch_obj.group(1)
t = str(int(time.time() * 1000))
captcha_url = 'https://www.zhihu.com/captcha.gif?r='+t+"&type=login"
return [scrapy.Request(
captcha_url,headers=self.headers,callback=self.get_captcha)]
def get_captcha(self,response):
with open('captcha.jpg', 'wb') as f:
f.write(response.body)
f.close()
try:
im = Image.open('captcha.jpg')
im.show()
#im.close()
except:
print('find captcha by your self')
self.postdata['captcha'] = input("please input the captcha\n>").strip()
if self.postdata['_xsrf'] and self.postdata['captcha']:
post_url = 'https://www.zhihu.com/login/phone_num'
return [scrapy.FormRequest(
url=post_url,
formdata=self.postdata,
headers=self.headers,
callback=self.check_login
)]
def check_login(self,response):
json_text = json.loads(response.text)
if 'msg' in json_text and json_text['msg'] == '登錄成功':
for url in self.start_urls:
yield scrapy.Request(url,dont_filter=True,headers=self.headers) #no callback , turn into parse
信息提取
思路
Scrapy
獲得了cookie
就可以登陸上知乎了,剩下的就是爬蟲邏輯和信息的提取具體實現了
具體的邏輯是從Aljun那里獲得的靈感,首先從一個大V開始(比如我,哈哈哈~) 獲得所以其所關注的人,之后再獲得這些人的信息將一些小號給過濾掉并錄入數據庫,之后在從其關注的人再獲得其所關注的人,再獲得信息錄入數據庫,就這樣不間斷的獲取下去,而Scrapy
自身就遵循了深度優先的算法
觀察下知乎的頁面的請求流程可以發現知乎用戶模塊前后端是分離的,知乎后端的api
看起來也和規范,前端用ajax
到后端的API
去拿數據,模板渲染等工作交給了react
由于每刷新一次頁面都需要發起Ajax
請求到后端去拿數據(為了保證數據的實時性),我們可以用開發者工具
調試頁面,刷新一次將http
請求拿出來看下所有請求的URL,沒有被緩存的請求都觀察一番就教容易找出了Ajax
請求的接口
首先我們先設計數據庫,這里使用MySQL
,我們可以根據感興趣的可以得到的用戶信息數據來設計我們的數據庫,知乎提供了一個API接口來獲得數據(先看看,我沒有用到這個接口)
知乎開放的可以獲取一個用戶的具體信息的API
https://www.zhihu.com/api/v4/members/zhu-xiao-fei-47-24
,其中url中編碼一些查詢參數,就可以獲得用戶對應的信息
https://www.zhihu.com/api/v4/members/zhu-xiao-fei-47-24?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_force_renamed%2Cis_bind_sina%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[%3F(type%3Dbest_answerer)].topics
向這個接口請求發起一個GET
請求,就可以獲得后臺發送來的JSON
數據,這個信息是比較完善的,當我們知道可以獲取哪些信息,找出自己關注的信息,就可以設計我們的數據庫了,
這里需要注意的是,顯然這個數據太龐大了,我們應該根據我們的需求編碼不同的參數進去從而獲得我們想要的數據,從而減少請求的JSON
數據的大小,節省帶寬
如我們設計的Item
是以下結構(和mysql
中的數據表的列相互對應)
class ZhihuUserItem(Item):
name = scrapy.Field()
id = scrapy.Field()
url_token = scrapy.Field()
headline = scrapy.Field()
answer_count = scrapy.Field()
articles_count = scrapy.Field()
gender = scrapy.Field()
avatar_url = scrapy.Field()
user_type = scrapy.Field()
following_count = scrapy.Field()
follower_count = scrapy.Field()
thxd_count = scrapy.Field()
agreed_count = scrapy.Field()
collected_count = scrapy.Field()
badge = scrapy.Field()
craw_time = scrapy.Field()
而我想獲得這樣的JSON
數據:
{
"following_count": 35,
"user_type": "people",
"id": "113d0e23a9ada1a61faf0272b4acf6c4",
"favorited_count": 38,
"voteup_count": 31,
"headline": "學生",
"url_token": "zhu-xiao-fei-47-24",
"follower_count": 22,
"avatar_url_template": "https://pic2.zhimg.com/20108b43c7b928229ba5cfafccca1235_{size}.jpg",
"name": "朱曉飛",
"thanked_count": 17,
"gender": 1,
"articles_count": 0,
"badge": [ ],
"answer_count": 32,
}
可以編碼這樣的請求進去
https://www.zhihu.com/api/v4/members/zhu-xiao-fei-47-24?include=gender%2Cvoteup_count%2Cthanked_Count%2Cfollower_count%2Cfollowing_count%2Canswer_count%2Carticles_count%2Cfavorite_count%2Cfavorited_count%2Cthanked_count%2Cbadge[%3F(type%3Dbest_answerer)].topics
同理
知乎后臺開放了獲取一個用戶關注者的API
https://www.zhihu.com/api/v4/members/zhu-xiao-fei-47-24/followees
,顯然這個接口和用戶相關如zhu-xiao-fei-47-24
,這是用戶的一個屬性url_token
,我們可以利用用戶的url_token
來拼接出獲得其關注者的url
由于查詢的HTTP Method
是Get
,查詢的參數是編碼到url
中,我們也可以在url
中encode
一些請求的參數進去,來獲得對應的數據,如
https://www.zhihu.com/api/v4/members/zhu-xiao-fei-47-24/followees?include=data[*].answer_count%2Carticles_count%2Cgender%2Cfollower_count%2Cis_followed%2Cis_following%2Cbadge[%3F(type%3Dbest_answerer)].topics&offset=20&limit=20
向這個請求發起一個GET
請求,就可以獲得后臺發送來的Json
數據,截取部分實例如下:
{
"paging": {
"is_end": true,
"totals": 35,
"previous": "http://www.zhihu.com/api/v4/members/zhu-xiao-fei-47-24/followees?include=data%5B%2A%5D.answer_count%2Carticles_count%2Cgender%2Cfollower_count%2Cis_followed%2Cis_following%2Cbadge%5B%3F%28type%3Dbest_answerer%29%5D.topics&limit=20&offset=0",
"is_start": false,
"next": "http://www.zhihu.com/api/v4/members/zhu-xiao-fei-47-24/followees?include=data%5B%2A%5D.answer_count%2Carticles_count%2Cgender%2Cfollower_count%2Cis_followed%2Cis_following%2Cbadge%5B%3F%28type%3Dbest_answerer%29%5D.topics&limit=20&offset=40"
},
"data": [
{
"is_followed": false,
"avatar_url_template": "https://pic1.zhimg.com/85fa1149f2d02e930498508dc71e6790_{size}.jpg",
"user_type": "people",
"answer_count": 391,
"is_following": true,
"url": "http://www.zhihu.com/api/v4/people/29d476a5746f5dd5ae8a296354e817de",
"type": "people",
"url_token": "chexiaopang",
"id": "29d476a5746f5dd5ae8a296354e817de",
"articles_count": 28,
"name": "車小胖",
"headline": "網絡主治大夫,專治疑難雜癥",
"gender": 1,
"is_advertiser": false,
"avatar_url": "https://pic1.zhimg.com/85fa1149f2d02e930498508dc71e6790_is.jpg",
"is_org": false,
"follower_count": 25580,
"badge": [
{
"topics": [
{
"url": "http://www.zhihu.com/api/v4/topics/19572894",
"avatar_url": "https://pic3.zhimg.com/e0bd139b2_is.jpg",
"name": "計算機網絡",
"introduction": "計算機網絡( <a href=\"http://www.wikiwand.com/en/Computer_Networks\" data-editable=\"true\" data-title=\"Computer Networks\">Computer Networks</a> )指將地理位置不同的多臺計算機及其外部設備,通過通信線路連接起來,在網絡操作系統及網絡通信協議的管理和協調下,實現資源共享和信息傳遞的計算機系統。",
"type": "topic",
"excerpt": "計算機網絡( Computer Networks )指將地理位置不同的多臺計算機及其外部設備,通過通信線路連接起來,在網絡操作系統及網絡通信協議的管理和協調下,實現資源共享和信息傳遞的計算機系統。",
"id": "19572894"
}
],
"type": "best_answerer",
"description": "優秀回答者"
}
]
},
//.... 還有19個用戶的數據
]
}
可以看到,在這個API里也可以返回關注用戶的信息,也就是每一個data
字段里面的信息。這就是我們要的接口了!
我們可以根據我們的需求構造出URL
去獲取我們想要的對應的數據。這個接口可以加三個參數
第一個include
就是我們可以請求到的用戶的信息,第二個offset
是偏移量表征當前返回的第一個記錄相對第一個following person
的數量,第三個limit
是返回限制數量,后面兩個貌似起不到控制作用,所以可以無視,但是Spider
對于一個沒有提取過following person
的時候,需要將offset
設置為0。
而第一個參數include
就是關注人的信息,我們可以將用戶的屬性如感謝數使用thanked_Count%2C
拼接起來:所以根據上面的需求,我們可以這么編碼
https://www.zhihu.com/api/v4/members/zhu-xiao-fei-47-24/followees?include=data[*].gender%2Cvoteup_count%2Cthanked_Count%2Cfollower_count%2Cfollowing_count%2Canswer_count%2Carticles_count%2Cfavorite_count%2Cfavorited_count%2Cthanked_count%2Cbadge[%3F(type%3Dbest_answerer)].topics&offset=20&limit=20
請求這個接口,就可以獲得我們數據庫所需要的信息,并且可以不傳輸大量的數據,如下:
{
"paging": {
"is_end": true,
"totals": 35,
"previous": "http://www.zhihu.com/api/v4/members/zhu-xiao-fei-47-24/followees?include=data%5B%2A%5D.gender%2Cvoteup_count%2Cthanked_Count%2Cfollower_count%2Cfollowing_count%2Canswer_count%2Carticles_count%2Cfavorite_count%2Cfavorited_count%2Cthanked_count%2Cbadge%5B%3F%28type%3Dbest_answerer%29%5D.topics&limit=20&offset=0",
"is_start": false,
"next": "http://www.zhihu.com/api/v4/members/zhu-xiao-fei-47-24/followees?include=data%5B%2A%5D.gender%2Cvoteup_count%2Cthanked_Count%2Cfollower_count%2Cfollowing_count%2Canswer_count%2Carticles_count%2Cfavorite_count%2Cfavorited_count%2Cthanked_count%2Cbadge%5B%3F%28type%3Dbest_answerer%29%5D.topics&limit=20&offset=40"
},
"data": [
{
"avatar_url_template": "https://pic1.zhimg.com/85fa1149f2d02e930498508dc71e6790_{size}.jpg",
"following_count": 118,
"user_type": "people",
"answer_count": 391,
"headline": "網絡主治大夫,專治疑難雜癥",
"url_token": "chexiaopang",
"id": "29d476a5746f5dd5ae8a296354e817de",
"favorite_count": 0,
"articles_count": 28,
"type": "people",
"name": "車小胖",
"url": "http://www.zhihu.com/api/v4/people/29d476a5746f5dd5ae8a296354e817de",
"gender": 1,
"favorited_count": 27051,
"is_advertiser": false,
"avatar_url": "https://pic1.zhimg.com/85fa1149f2d02e930498508dc71e6790_is.jpg",
"is_org": false,
"thanked_count": 7161,
"follower_count": 25588,
"voteup_count": 30449,
"badge": [
{
"topics": [
{
"introduction": "計算機網絡( <a href=\"http://www.wikiwand.com/en/Computer_Networks\" data-editable=\"true\" data-title=\"Computer Networks\">Computer Networks</a> )指將地理位置不同的多臺計算機及其外部設備,通過通信線路連接起來,在網絡操作系統及網絡通信協議的管理和協調下,實現資源共享和信息傳遞的計算機系統。",
"avatar_url": "https://pic3.zhimg.com/e0bd139b2_is.jpg",
"name": "計算機網絡",
"url": "http://www.zhihu.com/api/v4/topics/19572894",
"type": "topic",
"excerpt": "計算機網絡( Computer Networks )指將地理位置不同的多臺計算機及其外部設備,通過通信線路連接起來,在網絡操作系統及網絡通信協議的管理和協調下,實現資源共享和信息傳遞的計算機系統。",
"id": "19572894"
}
],
"type": "best_answerer",
"description": "優秀回答者"
}
]
},
//.... 還有19個用戶的數據
]
}
注意
在我們獲取我們想要的數據的時候,我們的爬蟲應該遵守一個原則就是:
盡可能減少我們的HTTP次數
在我們調整請求的URL
之后,相當于一個HTTP
請求,就可以獲得20
個item
,而不是一個請求獲得url_token
,每一個用戶的信息再需要一次http request
獲得,光這項的修改相當于提升了爬蟲20
倍的性能,當然說的有些夸張。但是,爬蟲的瓶頸逐漸不是信息的獲取,可能性能會損耗在在我們的數據庫的寫入
實現
此時,即可在模擬登陸的基礎上,完善我們的spider
,主要增加parse
這個實例方法
following_api = "https://www.zhihu.com/api/v4/members/{}/followees?include=data[*].gender%2Cvoteup_count%2Cthanked_Count%2Cfollower_count%2Cfollowing_count%2Canswer_count%2Carticles_count%2Cfavorite_count%2Cfavorited_count%2Cthanked_count%2Cbadge[%3F(type%3Dbest_answerer)].topics&offset=20&limit=20"
class ZhihuSpider(scrapy.Spider):
start_urls = [following_api.format('teng-xun-ke-ji')]
'''
模擬登陸的代碼
'''
def parse(self, response):
jsonresponse = json.loads(response.body_as_unicode())
if not jsonresponse['paging']['is_end']:
yield scrapy.Request(url=jsonresponse['paging']['next'])
if jsonresponse['data']:
for data in jsonresponse['data']:
url_token = data.get('url_token')
if url_token:
yield scrapy.Request(url=following_api.format(url_token))
agreed_count = data['voteup_count']
thxd_count = data['thanked_count']
collected_count = data['favorited_count']
if thxd_count or collected_count:
item_loader = ZhihuUserItemLoader(item=ZhihuUserItem(), response=response)
item_loader.add_value('name',data['name'])
item_loader.add_value('id',data['id'])
item_loader.add_value('url_token',data['url_token'])
item_loader.add_value('headline',data['headline']
if data['headline'] else "無")
item_loader.add_value('answer_count',data['answer_count'])
item_loader.add_value('articles_count',data['articles_count'])
item_loader.add_value('gender',data['gender']
if data['gender'] else 0)
item_loader.add_value('avatar_url',data['avatar_url_template'].format(size='xl'))
item_loader.add_value('user_type',data['user_type'])
item_loader.add_value('badge',','.join([badge.get('description') for badge in data['badge']])
if data.get('badge') else "無")
item_loader.add_value('follower_count',data['follower_count'])
item_loader.add_value('following_count',data['following_count'])
item_loader.add_value('agreed_count',agreed_count)
item_loader.add_value('thxd_count',thxd_count)
item_loader.add_value('collected_count',collected_count)
item_loader.add_value('craw_time',datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
zhihu_item = item_loader.load_item()
yield zhihu_item
數據入庫
獲得到數據,即可將item
的信息就可以插入到MySQL
中,可以添加一個pipeline
class MysqlTwsitedPipeline(object):
def __init__(self,dbpool):
self.dbpool = dbpool
@classmethod
def from_settings(cls,settings):
dbpara=dict(
host=settings['MYSQL_HOST'],
db=settings['MYSQL_NAME'],
user=settings['MYSQL_USER'],
passwd=settings['MYSQL_PASS'],
charset='utf8',
cursorclass=MySQLdb.cursors.DictCursor,
use_unicode=True
)
dbpool = adbapi.ConnectionPool("MySQLdb",**dbpara)
return cls(dbpool)
def process_item(self, item, spider):
query = self.dbpool.runInteraction(self.do_insert,item)
query.addErrback(self.handle_error)
return item
def handle_error(self,failure):
print(failure)
def do_insert(self,cursor,item):
insert_sql = item.get_insert_sql()
cursor.execute(insert_sql)
完整的item
:
class ZhihuUserItem(Item):
name = scrapy.Field()
id = scrapy.Field()
url_token = scrapy.Field()
headline = scrapy.Field()
answer_count = scrapy.Field()
articles_count = scrapy.Field()
gender = scrapy.Field()
avatar_url = scrapy.Field()
user_type = scrapy.Field()
following_count = scrapy.Field()
follower_count = scrapy.Field()
thxd_count = scrapy.Field()
agreed_count = scrapy.Field()
collected_count = scrapy.Field()
badge = scrapy.Field()
craw_time = scrapy.Field()
def get_insert_sql(self):
insert_sql = '''
replace into zhihu_user1 values('{}','{}','{}','{}',{},{},{},'{}','{}',{},{},{},{},{},'{}','{}')
'''.format(self['name'], self['id'], self['url_token'], self['headline'], self['answer_count'],
self['articles_count'], self['gender'], self['avatar_url'], self['user_type'],
self['following_count'], self['follower_count'], self['thxd_count'],
self['agreed_count'], self['collected_count'], self['badge'], self['craw_time'])
return insert_sql
在pipline
的handle_error
(異常處理處)函數內打上斷點,使用DEBUG
調試程序,觀察到有數據入庫即可
然而運行我們的project
,沒抓到幾百個用戶數據,就會出現http 429
甚至http 403
的情況
http 429解決辦法
http 429
意思請求的速率太快,顯然知乎后臺開放的API
會做一些調用限制,比如對IP
、調用的用戶等。
Scrapy
目前我沒有想到行知有效的方式,我認為最為方便的就是設置下載延時,在setting.py
文件中設置DOWNLOAD_DELAY
這個變量
并隨機切換代理和User-Agent
,編寫Scrapy
的middleware
,如下:
class ProxyMiddleware(object):
# overwrite process request
def process_request(self, request, spider):
# Set the location of the proxy
proxy_ip_list = [
'http://127.0.0.1:9999',
'http://120.xxx.x.x:xxx',
#....
]
user_agent_list = [
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 "
"(KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1",
"Mozilla/5.0 (X11; CrOS i686 2268.111.0) AppleWebKit/536.11 "
"(KHTML, like Gecko) Chrome/20.0.1132.57 Safari/536.11",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.6 "
"(KHTML, like Gecko) Chrome/20.0.1092.0 Safari/536.6",
"Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.6 "
"(KHTML, like Gecko) Chrome/20.0.1090.0 Safari/536.6",
"Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.1 "
"(KHTML, like Gecko) Chrome/19.77.34.5 Safari/537.1",
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/536.5 "
"(KHTML, like Gecko) Chrome/19.0.1084.9 Safari/536.5",
"Mozilla/5.0 (Windows NT 6.0) AppleWebKit/536.5 "
"(KHTML, like Gecko) Chrome/19.0.1084.36 Safari/536.5",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 "
"(KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3",
"Mozilla/5.0 (Windows NT 5.1) AppleWebKit/536.3 "
"(KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_0) AppleWebKit/536.3 "
"(KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3"
]
proxy_ip = random.choice(proxy_ip_list)
ua = random.choice(user_agent_list)
request.headers.setdefault('User-Agent', ua)
request.meta['proxy'] = proxy_ip
print('the Current ip address is', proxy_ip)
在setting
中設置使用上編寫好的下載中間件
至此我們的爬蟲大部分已經完成了。再次運行我們的爬蟲,當爬數據到一定數量(也沒多少),開始報http 403
,拷貝對應的請求url
到瀏覽器中,發現需要重新填寫驗證碼。沒一會,知乎便對我的id
進行了限制,手機客戶端也無法正常瀏覽
我猜測,知乎可能根據cookie
對觸發請求閾值的用戶識別后禁止...所以需要在setting.py
中設置COOKIES_ENABLES=False
使用Oauth匿名爬取
前幾天看到靜謐的做法
其根本就不要模擬登陸,但是為了可以訪問到信息,需要探測出Oauth
的值加入到header
中
這個Oauth
相當一個令牌,我對其目前還不太了解,先不做闡述。
需要注意的是,我們在上面的ProxyMiddleware
中重寫了header
,所以需要在ProxyMiddleware
里面加上這個header
request.headers.setdefault('authorization',
'oauth c3cef7c66a1843f8b3a9e6a1e3160e20')
于是乎,我們只需要關注優質、穩定的代理,設置好下載延時,就可以進行爬取了
當我們以匿名的形式,也就沒有之前模擬登陸的許多限制,但是也是需要注意設置延時和代理、隨機User-Agent
。
在單機設置為DOWNLOAD_DELAY = 0.4
,設置兩個代理的情況下,每小時大概能抓到2W+
的用戶數據,以這種形式我們的爬蟲的速率已經算是比較高了。