模擬登錄Github

這裏不討論用 Github API 的狀況,僅僅以 Github 來講明模擬登錄

先嚐試用真實瀏覽器登錄,登錄成功後在開發者工具的 Network 選項卡中捕獲 Session 文件。能夠看到,登錄所須要的數據不單單是 email(或用戶名) 和密碼,還須要其它的 3 個字段,而這 3 個字段普通用戶在真實瀏覽器中是沒法填寫的(也無需填寫,這仨字段會自動附加到表單中提交)。html

login form data

其中的 commit、utf8 的值是不變的,只有 authenticity_token 字段的值是每次登錄都不同的(爲的就是區分人類與爬蟲程序),authenticity_token 字段是在 https://github.com/login (登錄頁面,未登錄狀態)的 from 元素下的一個隱含字段(不顯示在瀏覽器中),其 type 屬性值爲 hiddenpython

下圖展現了(從新)登錄頁面的源碼,其中 type 屬性爲 hidden 的 input 字段中的 authenticity_token 屬性的值就是須要提取出來做爲表單數據的一部分提交至服務器git

authenticity token

從下圖能夠看到響應碼(Status Code)是 302 found 表示重定向跳轉至其它 url,這裏跳轉至 https://github.com,也就是說,登錄成功後就跳轉至 Github 首頁(即我的主頁)github

雖然是在 https://github.com/login 頁面中登錄,但登錄時是向 https://github.com/session 提交表單數據,因此在 session 響應中惋惜查看到已提交的表單數據。segmentfault

login form data

上圖展現了登錄成功後,已提交的表單數據,能夠發現 authenticity_token 字段的值和登錄前的值是一致的(email、password 字段因爲是明文,因此這裏打碼了)api

能保持登錄狀態的緣由是登錄成功後生成 Cookies 的功勞,不過 Cookies 通常不是永久有效的,若是但願長期處於登錄狀態,須要每隔一段時間檢測下 Cookies 是否還有效(或進行異常處理),失效的話就須要從新提交表單生成新的 Cookies。瀏覽器

代碼實現

使用的庫安全

  • requests
  • pyquery

攜帶 Cookies 模擬登錄 Github 的例子

代碼中的表單數據 post_data 的 login、password 這倆字段分別須要改成自已的 email(或用戶名)及密碼服務器

import requests
from pyquery import PyQuery as pq

headers = {
    'Referer': 'https://github.com/',
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36',
    'Host': 'github.com',
}
login_url = 'https://github.com/login'
post_url = 'https://github.com/session'
logined_url = 'https://github.com/settings/profile'
keys_url = "https://github.com/settings/keys"

# 提取隱含字段 authenticity_token 的值,登錄須要提交表單,而提交表單須要該值
login_r = requests.get(login_url, headers=headers)
doc = pq(login_r.text)
token = doc('input[name="authenticity_token"]').attr("value").strip()
print(token)

# 構造表單數據
post_data = {
    'commit': 'Sign in',
    'utf8': '✓',
    'authenticity_token': token,
    'login': email_or_name,
    'password': password,
}
# 模擬登錄必須攜帶 Cookies
post_r = requests.post(post_url, data=post_data, headers=headers, cookies=login_r.cookies.get_dict())
# 能夠發現響應的 url 是 https://github.com,而不是 https://github.com/session
# 由於模擬登錄成功後就 302 重定向跳轉至 "https://github.com" 了
print(post_r.url)
doc = pq(post_r.text)
# 輸出項目列表
print(doc("div.Box-body > ul > li").text().split())

# 請求其它 github 頁面,只要附加了能維持登錄狀態的 Cooikes 就能夠訪問只有登錄纔可訪問的頁面內容
logined_r = requests.get(logined_url, headers=headers, cookies=post_r.cookies.get_dict())
doc = pq(logined_r.text)
page_title = doc("title").text()
user_profile_bio = doc("#user_profile_bio").text()
user_profile_company = doc("#user_profile_company").attr("value")
user_profile_location = doc("#user_profile_location").attr("value")
print(f"頁面標題:{page_title}")
print(f"用戶資料描述:{user_profile_bio}")
print(f"用戶資料公司:{user_profile_company}")
print(f"用戶資料地點:{user_profile_location}")

# 使用 logined_r 的 Cookies 也能夠
keys_r = requests.get(keys_url, headers=headers, cookies=post_r.cookies.get_dict())
doc = pq(keys_r.text)
# SSH keys Title
doc('#ssh-key-29454773 strong.d-block').text()

顯式傳入 Cookies 、headers 仍是挺麻煩的,萬一有個請求沒有攜帶完整的 Cookies,可能就沒法獲得正確的響應。cookie

爲了省略每次都要手動傳入 Cookies 的麻煩,下面使用另外一種方式模擬登錄 Github

利用 Session 對象維持 Github 模擬登錄狀態

  1. 構造一個 session 對象;
  2. 使用 session 對象進行請求

代碼

其中使用 session.headers 維持每次會話的 headers 不變

爲了安全,利用內置模塊 getpass 輸入不可見的密碼(注意密碼必定不能錯)

import getpass

import requests
from pyquery import PyQuery as pq

class Login(object):
    def __init__(self):
        base_url = 'https://github.com/'
        # 登錄 url 
        self.login_url = base_url +'login'
        # 提交表單的 api
        self.post_url = base_url +'session'
        # 我的資料頁面的 url
        self.logined_url = base_url +'settings/profile'
        # 構造一個會話對象
        self.session = requests.Session()
        # 自定義請求頭
        self.session.headers = {
            'Referer': 'https://github.com/',
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36',
            'Host': 'github.com'
        }

    def token(self):
        # 請求登錄頁面
        response = self.session.get(self.login_url)
        # 提取 authenticity_token 的 value,
        doc = pq(response.text)
        token = doc('input[name="authenticity_token"]').attr("value").strip()
        return token
    
    def login(self, email, password):
        token = self.token()
        # 構造表單數據
        post_data = {
            'commit': 'Sign in',
            'utf8': '✓',
            'authenticity_token': token,
            'login': email,
            'password': password
        }
        # 發送 POST 請求,它會 302 重定向至 'https://github.com/',也就是響應 'https://github.com/' 的頁面
        response = self.session.post(self.post_url, data=post_data)
        # 能夠發現 302 重定向至 'https://github.com/'
        print(f"\n請求 url:{response.url}")
        if response.status_code == 200:
            print("status_code: 200")
            self.home(response.text)

        # 請求我的資料頁
        response = self.session.get(self.logined_url)
        if response.status_code == 200:
            print("status_code: 200")
            self.profile(response.text)

    def home(self, html):
        doc = pq(html)
        # 提取用戶名
        user_name = doc("summary > span").text().strip()
        print(f"用戶名:{user_name}")

        # 提取倉庫列表        
        Repositories = doc("div.Box-body > ul > li").text().split()
        for Repositorie in Repositories:
            print(Repositorie)
    
    def profile(self, html):
        doc = pq(html)
        page_title = doc("title").text()
        user_profile_bio = doc("#user_profile_bio").text()
        user_profile_company = doc("#user_profile_company").attr("value")
        user_profile_location = doc("#user_profile_location").attr("value")
        print(f"頁面標題:{page_title}")
        print(f"用戶資料描述:{user_profile_bio}")
        print(f"用戶資料公司:{user_profile_company}")
        print(f"用戶資料地點:{user_profile_location}")

    def main(self):
        email = input("email or username: ")
        # 輸入的密碼不可見,注意密碼必定不能錯
        password = getpass.getpass("password:")
        self.login(email=email, password=password)

if __name__ == "__main__":
    login = Login()
    login.main()
運行效果

run github_login.py

參考資料

  • 本文參考 《Python 3 網絡爬蟲開發實戰》 —— 10.1 模擬登錄並爬取 GitHub
  • 隱含字段參考了 《Python網絡數據採集》 —— 12.3 常見表單安全措施

閱讀更多

相關文章
相關標籤/搜索