基于Session和Cookie的模拟登录爬取实战
本节我们通过实例讲解基于 Session 和 Cookie 模拟登录并爬取数据的流程。
案例介绍
这里用到的案例网站是 https://login2.scrape.center/ ,访问这个网站,会打开一个登录页面,如图 10-1 所示。
图10-1 案例网站的登录页面
输入用户名和密码(都是 admin),然后点击登录按钮。登录成功后,我们便可以看到一个熟悉的页面,如图 10-2 所示。
图10-2 登录成功后的页面
这个网站是基于传统的 MVC 模式开发的,因此比较适合基于 Session 加 Cookie 的模式模拟登录。
模拟登录
对于这个网站,如果要模拟登录,需要先分析登录过程中发生了什么。打开开发者工具,重新执行登录操作,查看登录过程中产生的请求,如图 10-3 所示。
图10-3 登录过程中产生的请求
从图 10-3 中可以看到,在登录的瞬间,浏览器发起了一个 POST 请求,目标 URL 是 https://login2.scrape.center/login ,并通过表单提交的方式向服务器提交了登录数据,其中包括 username 和 password 两个字段,返回的状态码是 302,Response Headers 的 location 字段为根页面,同时 Response Headers 还包含 Set-Cookie 字段,其中设置了 sessionid。
由此我们可以想到,要实现模拟登录,只需要模拟这个 POST 请求就好了。那我们用代码实现一下吧!
每次发出的请求默认都是独立且互不干扰的,例如第一次调用 post 方法模拟登录了网站,紧接着调用 get 方法请求了主页面。这两个请求是完全独立的,第一次请求获取的 Cookie 并不能传给第二次请求,因此常规的顺序调用无法达到模拟登录的效果。下面用代码实现这个例子:
import requests
from urllib.parse import urljoin
BASE_URL = 'https://login2.scrape.center/'
LOGIN_URL = urljoin(BASE_URL, '/login')
INDEX_URL = urljoin(BASE_URL, '/page/1')
USERNAME = 'admin'
PASSWORD = 'admin'
response_login = requests.post(LOGIN_URL, data={
'username': USERNAME,
'password': PASSWORD
})
response_index = requests.get(INDEX_URL)
print('Response Status', response_index.status_code)
print('Response URL', response_index.url)
这单我们先定义了 3 个 URL、用户名和密码,然后调用 requests 库的 post 方法请求了登录页面进行模拟登录,紧接着调用 get 方法请求网站首页来获取页面内容,它能正常获取数据吗?由于 requests 可以自动处理重定向,所以在最后把响应的 URL 打印出来,如果结果是 INDEX_URL,就证明模拟登录成功并成功获取了网站首页的内容,如果结果是 LOGIN_URL,就说明跳回了登录页面,模拟登录失败。
运行一下代码,结果如下:
Response Status 200 Response URL https://login2.scrape.center/login?next=/page/1
可以看到,最后打印的页面 URL 是登录页面的 URL。这里也可以通过 response_index 的 text 属性来看一下页面源码,这里就是登录页面的源码内容,由于内容较多,这里就不再输出对比了。
总之,这个现象说明我们并没有成功完成模拟登录,这就印证了按序调用 requests 的 post、get 方法是发出了两个请求,两次对应的 Session 不是同一个,这里我们只是模拟了第一个 Session,并不影响第二个 Session 的状态,因此模拟登录也就无效了。
那怎样才能实现正确的模拟登录呢?Session 和 Cookie 的用法我们在本章开头就介绍了,模拟登录的关键在于两次发出的请求的 Cookie 相同。因此这里可以把第一次模拟登录后的 Cookie 保存下来,在第二次请求的时候加上这个 Cookie,代码改写如下:
import requests
from urllib.parse import urljoin
BASE_URL = 'https://login2.scrape.center/'
LOGIN_URL = urljoin(BASE_URL, '/login')
INDEX_URL = urljoin(BASE_URL, '/page/1')
USERNAME = 'admin'
PASSWORD = 'admin'
response_login = requests.post(LOGIN_URL, data={
'username': USERNAME,
'password': PASSWORD
}, allow_redirects=False)
cookies = response_login.cookies
print('Cookies', cookies)
response_index = requests.get(INDEX_URL, cookies=cookies)
print('Response Status', response_index.status_code)
print('Response URL', response_index.url)
由于 requests 具有自动处理重定向的能力,所以在模拟登录的过程中要加上 allow_redirects 参数并将值设置为 False,使 requests 不自动处理重定向。这里将登录之后服务器返回的响应内容赋值为 response_login 变量,然后调用 response_login 的 cookies 属性就可以获取了网站的 Cookie 信息。由于 requests 自动帮我们解析了响应头中的 Set-Cookie 字段并设置了 Cookie,因此不需要我们再去手动解析。
接着,调用 requests 的 get 方法请求网站的首页。和之前不同,这里的 get 方法增加了一个参数 cookies,传入的值是第一次模拟登录后获取的 Cookie,这样第二次请求就携带上了第一次模拟登录获取的 Cookie 信息,之后网站会根据里面的 SessionID 信息找到同一个 Session,并校验出当前发出请求的用户已经处于登录状态,然后返回正确的结果。
最后我们还是输出最终的 URL,如果结果是 INDEX_URL,就代表模拟登录成功并获取了有效数据否则代表模拟登录失败。
运行结果如下:
图中文字内容识别如下:
Cookies `<RequestsCookieJar[<Cookie sessionid=psnu8ij69foltecdswasccyzc6ud4itc for login2.scrape.center/>]>` Response Status 200 Response URL [https://login2.scrape.center/page/1](https://login2.scrape.center/page/1)
返回的是 INDEX_URL,这下没有问题了,模拟登录成功!此时还可以进一步输出 response_index 的 text 属性,看一下数据是否获取成功。
但其实可以发现,这种实现方式比较麻烦,每次请求都需要处理并传递一次 Cookie,有没有更简便的方法呢?
有的,可以直接借助 requests 内置的 Session 对象帮我们自动处理 Cookie,使用 Session 对象之后,requests 会自动保存每次请求后设置的 Cookie,并在下次请求时携带它,这样就变方便了。把刚才的代码简化一下:
import requests
from urllib.parse import urljoin
BASE_URL = 'https://login2.scrape.center/'
LOGIN_URL = urljoin(BASE_URL, '/login')
INDEX_URL = urljoin(BASE_URL, '/page/1')
USERNAME = 'admin'
PASSWORD = 'admin'
session = requests.Session()
response_login = session.post(LOGIN_URL, data={
'username': USERNAME,
'password': PASSWORD
})
cookies = session.cookies
print('Cookies', cookies)
response_index = session.get(INDEX_URL, cookies=cookies)
print('Response Status', response_index.status_code)
print('Response URL', response_index.url)
可以看到,这里声明了一个 Session 对象,然后每次发出请求的时候都直接调用 Session 对象的图中显示的文本内容识别如下:
post 方法或 get 方法就好了,使我们无须再关心 Cookie 的处理和传递问题。
运行结果如下:
Cookies `<RequestsCookieJar[<Cookie sessionid=ssngk1417en9vm73bb36hxiif05k10k13 for login2.scrape.center/>]>` Response Status 200 Response URL [https://login2.scrape.center/page/1](https://login2.scrape.center/page/1)
和刚才的结果完全一样。因此建议大家使用 Session 对象进行请求,这样实现起来会更加方便。
这个案例整体来讲其实比较简单,如果碰上复杂一点的网站,例如带有验证码、带有加密参数的网站,直接用 requests 并不能很好地模拟登录,那登录不了,整个页面就没法爬取了吗?有没有其他方式来解决这个问题呢?当然有,例如可以使用 Selenium 模拟浏览器的操作,进而实现模拟登录,然后获取登录成功的 Cookie,再把获取的 Cookie 交由 requests 爬取。
还是同样的页面,由 Selenium 实现模拟登录,后续的爬取则交给 requests,相关代码如下:
from urllib.parse import urljoin
from selenium import webdriver
import requests
import time
BASE_URL = 'https://login2.scrape.center/'
LOGIN_URL = urljoin(BASE_URL, '/login')
INDEX_URL = urljoin(BASE_URL, '/page/1')
USERNAME = 'admin'
PASSWORD = 'admin'
browser = webdriver.Chrome()
browser.get(BASE_URL)
browser.find_element_by_css_selector('input[name="username"]').send_keys(USERNAME)
browser.find_element_by_css_selector('input[name="password"]').send_keys(PASSWORD)
browser.find_element_by_css_selector('input[type="submit"]').click()
time.sleep(10)
# 从浏览器对象中获取 Cookie 信息
cookies = browser.get_cookies()
print('Cookies', cookies)
browser.close()
# 把 Cookie 信息放入请求中
session = requests.Session()
for cookie in cookies:
session.cookies.set(cookie['name'], cookie['value'])
response_index = session.get(INDEX_URL)
print('Response Status', response_index.status_code)
print('Response URL', response_index.url)
这里我们先使用 Selenium 打开 Chrome 浏览器,然后访问登录页面,模拟输入用户名和密码,并点击登录按钮。浏览器会提示登录成功,并跳转到主页面。
这时,调用 get_cookies 方法能获取当前浏览器的所有 Cookie 信息,这就是模拟登录成功之后的 Cookie,用它就能访问其他数据了。
之后,我们声明了一个 Session 对象,赋值给 session 变量,然后遍历了刚才获取的所有 Cookie 信息,将每个 Cookie 信息依次设置到 session 的 cookies 属性上,随后拿这个 session 请求网站首页,就能够获取想要的信息了而不会跳转到登录页面。
运行结果如下:
Cookies `[{'domain': 'login2.scrape.center', 'expiry': 1589043753.553155, 'httpOnly': True, 'name': 'sessionid', 'path': '/', 'sameSite': 'lax', 'secure': False, 'value': 'rdag7ttjqhvazavpxzj1y0tmze8izur'}]`
Response Status 200
Response URL [https://login2.scrape.center/page/1](https://login2.scrape.center/page/1)
图中显示的文本内容识别如下:
可以看到,这里的模拟登录和获取 Cookie 信息后的爬取都成功了。因此当碰到难以模拟登录的情况时,可以使用 Selenium 等模拟浏览器的操作方式,使用它获取模拟登录后的 Cookie,再用这个 Cookie 爬取其他页面就好了。
这里也再一次巩固了对前面结论的认识,即对于基于 Session 和 Cookie 验证的网站,模拟登录的关键是获取 Cookie。可以把这个 Cookie 保存下来或传递给其他程序继续使用,甚至可以持久化存储或传输给其他终端使用。另外,为了提高 Cookie 的利用率和降低封号风险,可以搭建一个账号池来实现 Cookie 的随机取用。