這裏不討論用 Github API 的狀況,僅僅以 Github 來講明模擬登錄
先嚐試用真實瀏覽器登錄,登錄成功後在開發者工具的 Network 選項卡中捕獲 Session 文件。能夠看到,登錄所須要的數據不單單是 email(或用戶名) 和密碼,還須要其它的 3 個字段,而這 3 個字段普通用戶在真實瀏覽器中是沒法填寫的(也無需填寫,這仨字段會自動附加到表單中提交)。html
其中的 commit、utf8 的值是不變的,只有 authenticity_token 字段的值是每次登錄都不同的(爲的就是區分人類與爬蟲程序),authenticity_token 字段是在 https://github.com/login
(登錄頁面,未登錄狀態)的 from
元素下的一個隱含字段(不顯示在瀏覽器中),其 type 屬性值爲 hidden。python
下圖展現了(從新)登錄頁面的源碼,其中 type 屬性爲 hidden 的 input 字段中的 authenticity_token 屬性的值就是須要提取出來做爲表單數據的一部分提交至服務器git
從下圖能夠看到響應碼(Status Code)是 302 found
表示重定向跳轉至其它 url,這裏跳轉至 https://github.com
,也就是說,登錄成功後就跳轉至 Github 首頁(即我的主頁)github
雖然是在 https://github.com/login
頁面中登錄,但登錄時是向 https://github.com/session
提交表單數據,因此在 session 響應中惋惜查看到已提交的表單數據。segmentfault
上圖展現了登錄成功後,已提交的表單數據,能夠發現 authenticity_token 字段的值和登錄前的值是一致的(email、password 字段因爲是明文,因此這裏打碼了)api
能保持登錄狀態的緣由是登錄成功後生成 Cookies 的功勞,不過 Cookies 通常不是永久有效的,若是但願長期處於登錄狀態,須要每隔一段時間檢測下 Cookies 是否還有效(或進行異常處理),失效的話就須要從新提交表單生成新的 Cookies。瀏覽器
使用的庫安全
代碼中的表單數據 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.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()