本人剛開始學習,可能有些不對的地方,希望大家指正~~
首先,我們分析下知乎登陸的接口,打開瀏覽器,到知乎的登陸界面,隨便輸入一個賬號密碼,查看點擊登陸它幹了些什麼(別輸入正確的,不然他就跳到首頁去啦~)
可以看到它調用了一個phone_num的接口(郵箱登陸同理就不演示了),再看下這個接口的參數
出了那個_xsrf外,其他參數根據名字大概都可以猜到了
我們先來看看這個_xsrf從哪裏來的,打開網頁源碼,查找_xsrf,發現有一個叫_xsrf的隱藏input標籤,值和我們傳入的這個參數是一樣的
這樣,我們就可以直接去取了
除了這個_xsrf外,我們還看到有一個叫captcha的參數,我們猜測它應該就是驗證碼,我們爲了看看這個驗證碼是如何獲得的,刷新一下看看
發現它調了這樣一個接口,我們把這個url複製下來,然後直接打開看看~發現正是我們的驗證碼,我們看這個驗證碼,他是讓我們點擊倒立的字,再看我們上傳的參數,除了那張圖片的大小外,還有一個input_points的參數,應該就是我們點擊的點的座標,服務器通過這個判斷我們是否點擊了倒立的字,如果我們這樣傳參數的話,驗證碼圖片的大小我們看上去是固定的,但是這個座標就比較麻煩了,我們再來分析一下這個請求驗證碼的url的參數,有3個參數,r,type,lang,最後一個參數好像是表示語言是中文,我們把它變爲en驗證一下,
發現請求變成了這樣一張圖片,這不就是直接輸入驗證碼的圖片了麼~如果可以用這個輸入驗證碼的話我們就可以解決驗證碼這個參數了
好了,請求的分析差不多就這樣了~下面我們可以開始寫代碼了
先一下我使用的python版本爲3.6
新建一個scrapy的項目,新建一個爬蟲(以scrapy的base模板新建~另外,這篇文章就做登錄的過程,item,pipelines,middleware,settings這些文件就用默認的就行了~)
我們想一下,這個登錄的過程中,我們有兩個接口需要調用,1是獲取驗證碼,2就是登錄,毫無疑問,獲取驗證碼應該優先調用,我們把我們的初始url定爲獲取驗證碼,再看一下我們上面驗證碼接口的參數,有個r的參數,是一串數字,我們優先應該想到是一個和時間有關的參數,我們先用當前時間來試試,我們的入口函數start_requests就變成下面這樣
def start_requests(self):
t = str(int(time.time() * 1000))
captcha_url = 'https://www.zhihu.com/captcha.gif?r=' + t + '&type=login&lang=en'
return [scrapy.Request(url=captcha_url, headers={},
callback=self.parser_captcha)]
debug 看下我們的回調函數的response
發現服務器返回了500的錯誤,並未進入我們的回調函數,再去看下我們的網絡請求的header,我們知道scrapy對於cookie等一系列值都已經封裝好了,但是下面有一個Use-Agent的參數,是需要我們傳的,它表示了我們的系統信息以及我們的瀏覽器信息等一系列信息,我們直接在我們的瀏覽器複製這個,在我們的header裏傳進去,再次debug,進入到回調函數裏了
看出body是一個二進制文件,我們把文件寫入本地看看是不是我們要的驗證碼圖片(注意:這裏要用pillow的Image來把圖片展示出來),發現我們的猜測都是正確的,打開就是我們的驗證碼圖片了,然後就是讀取驗證碼裏的信息問題了,這裏就用簡單的打開圖片我們手動輸入的方式,當然,還可以用雲打碼這種三方平臺來讀取驗證碼信息,就可以讓爬蟲全程不需要人來操作了
接下來,我們就需要去調用登錄的接口了,記得前面我們說的參數有個_xsrf的,我們先去取這個參數
_xsrf = response.xpath("//input[@name='_xsrf']/@value").extract_first()
參數都齊了,我們去調接口去~
def login(self, response):
xsrf = response.xpath("//input[@name='_xsrf']/@value").extract_first()
if xsrf is None:
return ''
post_url = 'https://www.zhihu.com/login/phone_num'
post_data = {
"_xsrf": xsrf,
"phone_num": 'your phone number',
"password": 'your password',
"captcha": response.meta['captcha']
}
return [scrapy.FormRequest(url=post_url, formdata=post_data, headers=self.header, callback=self.check_login)]
後面還寫了個檢查登錄是否成功的回調
def check_login(self, response):
js = json.loads(response.text)
if 'msg' in js and js['msg'] == '登錄成功':
for url in self.start_urls:
yield scrapy.Request(url=url, headers=self.header, dont_filter=True)
運行一下我們的爬蟲
嗯 ,成功了,接下來就可以去主頁上爬取自己感興趣的內容了
第一次寫博客,有很多東西可能描述的也不是很清楚,最後把這個爬蟲的完整代碼放上來吧
# -*- coding: utf-8 -*-
import json
import os
import scrapy
import time
from PIL import Image
class ZhihuloginSpider(scrapy.Spider):
name = 'zhihulogin'
allowed_domains = ['www.zhihu.com']
start_urls = ['https://www.zhihu.com/']
Agent = 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36'
header = {
'User-Agent': Agent,
}
def parse(self, response):
#主頁爬取的具體內容
pass
def start_requests(self):
t = str(int(time.time() * 1000))
captcha_url = 'https://www.zhihu.com/captcha.gif?r=' + t + '&type=login&lang=en'
return [scrapy.Request(url=captcha_url, headers=self.header, callback=self.parser_captcha)]
def parser_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(u'請到 %s 目錄找到captcha.jpg 手動輸入' % os.path.abspath('captcha.jpg'))
captcha = input("please input the captcha\n>")
return scrapy.FormRequest(url='https://www.zhihu.com/#signin', headers=self.header, callback=self.login, meta={
'captcha': captcha
})
def login(self, response):
xsrf = response.xpath("//input[@name='_xsrf']/@value").extract_first()
if xsrf is None:
return ''
post_url = 'https://www.zhihu.com/login/phone_num'
post_data = {
"_xsrf": xsrf,
"phone_num": 'your phone number',
"password": 'your password',
"captcha": response.meta['captcha']
}
return [scrapy.FormRequest(url=post_url, formdata=post_data, headers=self.header, callback=self.check_login)]
# 驗證返回是否成功
def check_login(self, response):
js = json.loads(response.text)
if 'msg' in js and js['msg'] == '登錄成功':
for url in self.start_urls:
yield scrapy.Request(url=url, headers=self.header, dont_filter=True)