Python爬取知乎與我所理解的爬蟲與反爬蟲

知乎已經成為了爬蟲的訓練場,本文利用Python中的requests庫,模擬登陸知乎,獲取cookie,保存到本地,然后這個cookie作為登陸的憑證,登陸知乎的主頁面,爬取知乎主頁面上的問題和對應問題回答的摘要。

關于知乎驗證碼登陸的問題,用到了Python上一個重要的圖片處理庫PIL,如果不行,就把圖片存到本地,手動輸入。

爬取知乎的關鍵的部分:模擬登陸

通過對知乎登陸是的抓包,可以發現登陸知乎,需要post三個參數,一個是賬號,一個是密碼,一個是xrsf。
這個xrsf隱藏在表單里面,每次登陸的時候,應該是服務器隨機產生一個字符串。所有,要模擬登陸的時候,必須要拿到xrsf。

用chrome (或者火狐 httpfox 抓包分析)的結果:


image.png

所以,必須要拿到xsrf的數值,注意這是一個動態變化的參數,每次都不一樣。


image.png

注意findall和find_all函數的區別。

拿到xsrf,下面就可以模擬登陸了。
使用requests庫的session對象,建立一個會話的好處是,可以把同一個用戶的不同請求聯系起來,直到會話結束都會自動處理cookies。

image.png

注意:cookies 是當前目錄的一個文件,這個文件保存了知乎的cookie,如果是第一個登陸,那么當然是沒有這個文件的,不能通過cookie文件來登陸。必須要輸入密碼。

def login(secret, account):
    # 通過輸入的用戶名判斷是否是手機號
    if re.match(r"^1\d{10}$", account):
        print("手機號登錄 \n")
        post_url = 'https://www.zhihu.com/login/phone_num'
        postdata = {
            '_xsrf': get_xsrf(),
            'password': secret,
            'remember_me': 'true',
            'phone_num': account,
        }
    else:
        if "@" in account:
            print("郵箱登錄 \n")
        else:
            print("你的賬號輸入有問題,請重新登錄")
            return 0
        post_url = 'https://www.zhihu.com/login/email'
        postdata = {
            '_xsrf': get_xsrf(),
            'password': secret,
            'remember_me': 'true',
            'email': account,
        }
    try:
        # 不需要驗證碼直接登錄成功
        login_page = session.post(post_url, data=postdata, headers=headers)
        login_code = login_page.text
        print(login_page.status_code)
        print(login_code)
    except:
        # 需要輸入驗證碼后才能登錄成功
        postdata["captcha"] = get_captcha()
        login_page = session.post(post_url, data=postdata, headers=headers)
        login_code = eval(login_page.text)
        print(login_code['msg'])
    session.cookies.save()
try:
    input = raw_input
except:
    pass

這是登陸的函數,通過login函數來登陸,post 自己的賬號,密碼和xrsf 到知乎登陸認證的頁面上去,然后得到cookie,將cookie保存到當前目錄下的文件里面。下次登陸的時候,直接讀取這個cookie文件。

#LWP-Cookies-2.0
Set-Cookie3: cap_id="\"YWJkNTkxYzhiMGYwNDU2OGI4NDUxN2FlNzBmY2NlMTY=|1487052577|4aacd7a27b11a852e637262bb251d79c6cf4c8dc\""; path="/"; domain=".zhihu.com"; path_spec; expires="2017-03-16 06:09:37Z"; version=0
Set-Cookie3: l_cap_id="\"OGFmYTk3ZDA3YmJmNDQ4YThiNjFlZjU3NzQ5NjZjMTA=|1487052577|0f66a8f8d485bc85e500a121587780c7c8766faf\""; path="/"; domain=".zhihu.com"; path_spec; expires="2017-03-16 06:09:37Z"; version=0
Set-Cookie3: login="\"NmYxMmU0NWJmN2JlNDY2NGFhYzZiYWIxMzE5ZTZiMzU=|1487052597|a57652ef6e0bbbc9c4df0a8a0a59b559d4e20456\""; path="/"; domain=".zhihu.com"; path_spec; expires="2017-03-16 06:09:57Z"; version=0
Set-Cookie3: q_c1="ee29042649aa4f87969ed193acb6cb83|1487052577000|1487052577000"; path="/"; domain=".zhihu.com"; path_spec; expires="2020-02-14 06:09:37Z"; version=0
Set-Cookie3: z_c0="\"QUFCQTFCOGdBQUFYQUFBQVlRSlZUVFVzeWxoZzlNbTYtNkt0Qk1NV0JLUHZBV0N6NlNNQmZ3PT0=|1487052597|dcf272463c56dd6578d89e3ba543d46b44a22f68\""; path="/"; domain=".zhihu.com"; path_spec; expires="2017-03-16 06:09:57Z"; httponly=None; version=0

這是cookie文件的內容

以下是源碼:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import requests
try:
    import cookielib
except:
    import http.cookiejar as cookielib
import re
import time
import os.path
try:
    from PIL import Image
except:
    pass

from bs4 import BeautifulSoup


# 構造 Request headers
agent = 'Mozilla/5.0 (Windows NT 5.1; rv:33.0) Gecko/20100101 Firefox/33.0'
headers = {
    "Host": "www.zhihu.com",
    "Referer": "https://www.zhihu.com/",
    'User-Agent': agent
}

# 使用登錄cookie信息
session = requests.session()
session.cookies = cookielib.LWPCookieJar(filename='cookies')
try:
    session.cookies.load(ignore_discard=True)
except:
    print("Cookie 未能加載")



def get_xsrf():
    '''_xsrf 是一個動態變化的參數'''
    index_url = 'https://www.zhihu.com'
    # 獲取登錄時需要用到的_xsrf
    index_page = session.get(index_url, headers=headers)
    html = index_page.text
    pattern = r'name="_xsrf" value="(.*?)"'
    # 這里的_xsrf 返回的是一個list
    _xsrf = re.findall(pattern, html)
    return _xsrf[0]





# 獲取驗證碼
def get_captcha():
    t = str(int(time.time() * 1000))
    captcha_url = 'https://www.zhihu.com/captcha.gif?r=' + t + "&type=login"
    r = session.get(captcha_url, headers=headers)
    with open('captcha.jpg', 'wb') as f:
        f.write(r.content)
        f.close()
    # 用pillow 的 Image 顯示驗證碼
    # 如果沒有安裝 pillow 到源代碼所在的目錄去找到驗證碼然后手動輸入
    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 captcha





def isLogin():
    # 通過查看用戶個人信息來判斷是否已經登錄
    url = "https://www.zhihu.com/settings/profile"
    login_code = session.get(url, headers=headers, allow_redirects=False).status_code
    if login_code == 200:
        return True
    else:
        return False


def login(secret, account):
    # 通過輸入的用戶名判斷是否是手機號
    if re.match(r"^1\d{10}$", account):
        print("手機號登錄 \n")
        post_url = 'https://www.zhihu.com/login/phone_num'
        postdata = {
            '_xsrf': get_xsrf(),
            'password': secret,
            'remember_me': 'true',
            'phone_num': account,
        }
    else:
        if "@" in account:
            print("郵箱登錄 \n")
        else:
            print("你的賬號輸入有問題,請重新登錄")
            return 0
        post_url = 'https://www.zhihu.com/login/email'
        postdata = {
            '_xsrf': get_xsrf(),
            'password': secret,
            'remember_me': 'true',
            'email': account,
        }
    try:
        # 不需要驗證碼直接登錄成功
        login_page = session.post(post_url, data=postdata, headers=headers)
        login_code = login_page.text
        print(login_page.status_code)
        print(login_code)
    except:
        # 需要輸入驗證碼后才能登錄成功
        postdata["captcha"] = get_captcha()
        login_page = session.post(post_url, data=postdata, headers=headers)
        login_code = eval(login_page.text)
        print(login_code['msg'])
    session.cookies.save()
try:
    input = raw_input
except:
    pass



## 將main的問題列表輸出在shell上面
def  getPageQuestion(url2):  
  mainpage = session.get(url2, headers=headers)
  soup=BeautifulSoup(mainpage.text,'html.parser')
  tags=soup.find_all("a",class_="question_link")
  #print tags

  for tag in tags:
    print tag.string

# 將main頁面上面的問題的回答的摘要輸出在shell上面
def getPageAnswerAbstract(url2):
    mainpage=session.get(url2,headers=headers)
    soup=BeautifulSoup(mainpage.text,'html.parser')
    tags=soup.find_all('div',class_='zh-summary summary clearfix')

    for tag in tags:
       # print tag
        print tag.get_text()
        print '詳細內容的鏈接 : ',tag.find('a').get('href')


def getPageALL(url2):
    #mainpage=session.get(url2,headers=headers)
    #soup=BeautifulSoup(mainpage.text,'html.parser')
    #tags=soup.find_all('div',class_='feed-item-inner')
    #print "def getpageall "
    mainpage=session.get(url2,headers=headers)
    soup=BeautifulSoup(mainpage.text,'html.parser')
    tags=soup.find_all('div',class_='feed-content')
    for tag in tags:
        #print tag
        print tag.find('a',class_='question_link').get_text()
        # 這裏有一點問題 bs 還是用的不是太熟練
        #print tag.find('a',class_='zh-summary summary clearfix').get_text()
        #print tag.find('div',class_='zh-summary summary clearfix').get_text()


if __name__ == '__main__':
    if isLogin():
        print('您已經登錄')
        url2='https://www.zhihu.com'
        # getPageQuestion(url2)
        #getPageAnswerAbstract(url2)
        getPageALL(url2)
    else:
        account = input('請輸入你的用戶名\n>  ')
        secret = input("請輸入你的密碼\n>  ")
        login(secret, account)

運行結果:


image.png

git鏈接:

https://github.com/zhaozhengcoder/Spider/tree/master/spider_zhihu

PPS:我所理解的爬蟲與反爬蟲策略

反爬蟲最基本的策略:

  1. 檢查瀏覽器http請求里面的user-agent字段
  2. 檢查http請求的referer(即當前的這個頁面是從哪個頁面跳轉過來的)

爬蟲策略:
這兩個都是在http協議的報文段的檢查,同樣爬蟲端可以很方便的設置這些字段的值,來欺騙服務器。

反爬蟲進階策略:
1.像知乎一樣,在登錄的表單里面放入一個隱藏字段,里面會有一個隨機數,每次都不一樣,這樣除非你的爬蟲腳本能夠解析這個隨機數,否則下次爬的時候就不行了。
2.記錄訪問的ip,統計訪問次數,如果次數太高,可以認為這個ip有問題。

爬蟲進階策略:
1.像這篇文章提到的,爬蟲也可以先解析一下隱藏字段的值,然后再進行模擬登錄。
2.爬蟲可以使用ip代理池的方式,來避免被發現。同時,也可以爬一會休息一會的方式來降低頻率。另外,服務器根據ip訪問次數來進行反爬,再ipv6沒有全面普及的時代,這個策略會很容易造成誤傷。(這個是我個人的理解)。

通過Cookie限制進行反爬蟲:
和Headers校驗的反爬蟲機制類似,當用戶向目標網站發送請求時,會再請求數據中攜帶Cookie,網站通過校驗請求信息是否存在Cookie,以及校驗Cookie的值來判定發起訪問請求的到底是真實的用戶還是爬蟲,第一次打開網頁會生成一個隨機cookie,如果再次打開網頁這個Cookie不存在,那么再次設置,第三次打開仍然不存在,這就非常有可能是爬蟲在工作了。

反爬蟲進進階策略:
1.數據投毒,服務器在自己的頁面上放置很多隱藏的url,這些url存在于html文件文件里面,但是通過css或者js使他們不會被顯示在用戶看到的頁面上面。(確保用戶點擊不到)。那么,爬蟲在爬取網頁的時候,很用可能取訪問這個url,服務器可以100%的認為這是爬蟲干的,然后可以返回給他一些錯誤的數據,或者是拒絕響應。

爬蟲進進階策略:
1.各個網站雖然需要反爬蟲,但是不能夠把百度,谷歌這樣的搜索引擎的爬蟲給干了(干了的話,你的網站在百度都說搜不到!)。這樣爬蟲應該就可以冒充是百度的爬蟲去爬。(但是ip也許可能被識破,因為你的ip并不是百度的ip)

反爬蟲進進進階策略:
給個驗證碼,讓你輸入以后才能登錄,登錄之后,才能訪問。

爬蟲進進進階策略:
圖像識別,機器學習,識別驗證碼。不過這個應該比較難,或者說成本比較高。

參考資料:
廖雪峰的python教程
靜覓的python教程
requests庫官方文檔
segmentfault上面有一個人的關于知乎爬蟲的博客,找不到鏈接了

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,841評論 25 708
  • 爬蟲文章 in 簡書程序員專題: like:128-Python 爬取落網音樂 like:127-【圖文詳解】py...
    喜歡吃栗子閱讀 21,916評論 4 411
  • 爬蟲文章 in 簡書程序員專題: like:128 - Python 爬取落網音樂 like:127 - 【圖文詳...
    treelake閱讀 29,567評論 33 638
  • 基礎知識 HTTP協議 我們瀏覽網頁的瀏覽器和手機應用客戶端與服務器通信幾乎都是基于HTTP協議,而爬蟲可以看作是...
    腩啵兔子閱讀 1,507評論 0 17
  • 高級特性:代碼越少,開發效率越高! 課題:構造一個1,3,5,7,9,...,99的列表。 普通方法: L = [...
    黃大臻Dzreal閱讀 309評論 0 0