这里不讨论用 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()