爬虫识别
搜索引擎与爬虫

爬虫系列:穿越网页表单与登录窗口进行采集(二)

上一期我们讲解了 Python Requests 库、提交一个基本表单、HTML 相关控件等内容。

本篇文章我们接着上一期文章介绍通过 Python Requests 提交文件和图像、处理登录 cookie、HTTP 基本接入认证以及其他表单相关问题。

提交文件和图像

虽然上传文件在网络上很普遍,但是对于网络数据采集其实不太常用。但是,如果你想为自己网站的文件上传一个测试实例,也可以通过 Python Requests 库实现。不管怎么说,掌握工作原理总是有用的。

下面是一个文件上传的源代码示例:

<label class="UploadPicture-wrapper">
    <input type="file" accept="image/png,image/jpeg" class="UploadPicture-input">
    <button type="button" class="Button UserCoverEditor-simpleEditButton DynamicColorButton DynamicColorButton--dark">
        <span style="display: inline-flex; align-items: center;">​</span> 编辑封面图片
    </button>
</label>

文件上传表单除了 <input> 标签里有一个 type 属性是 file,看起来和上一篇文章中的文字字段并没有什么两样。其实,Python Requests 库对这种表单的处理方式和之前非常相似:

import requests


def upload_image():
    files = {'uploadFile': open('files/2fe7243c7c113fad443b375a021801eb6277169d.png', 'rb')}
    r = requests.post("http://pythonscraping.com/pages/processing2.php", files=files)
    print(r.text)


if __name__ == '__main__':
    upload_image()

需要注意,这里提交给表单字段 uploadFile 的值不一定是一个简单的字符串,而是一个使用 open 函数打开的 Python 文件对象。在这个例子当中,我们提交了一个保存在我们电脑上的图像文件,文件路径是相对于这个 Python 程序所在的位置。

处理登录和 cookie

到此为止,我们介绍过的大多数表单都允许你向网站提交信息,或者让你在提交表单后立即看到想要的页面信息。那么,那些表单和登录表单(当你浏览网站时让你保持“已登录“状态)有什么不同?

大多数的现代网站使用的是 cookie 跟踪用户是否已登录的状态信息。一旦网站验证了你的登录权证,他就会将它们保存在你浏览器的 cookie 中,里面通常包含一个服务器生成的令牌,登录有效时限和登录状态追踪信息。网站会把这个 cookie 当作信息验证的凭据,在你浏览网站的每个页面时出示给服务器。在 20 世纪 90 年代中期广泛使用 cookie 之前,保证用户安全验证并追踪用户是网站上的一大问题。

虽然 cookie 为网络开发者解决了大问题,但同时却为网络爬虫带来了大问题。你可以一整天只提交一次登录表单,但是如果你没有一直关注表单后回传给你的那个 cookie,那么一段时间以后再次访问新页面时,你的登录状态就会丢失,需要重新登录。

现在我们有一个博客的管理后台,我们需要登录才能发布文章和上传图片,下面我们通过使用 Python Requests 模拟登录,并追踪 cookie,下面是代码示例:

import requests
from bs4 import BeautifulSoup
from requests import Session, exceptions

from utils import connection_util

class GetCookie(object):
    def __init__(self):
        self._session = Session()
        self._init_connection = connection_util.ProcessConnection()

    def get_cookie_by_login(self):
        # 另外一个 session 中
        get_token=self.request_verification_token()
        if get_token:
            params = {'__RequestVerificationToken': get_token, 'Email': 'abc@pdf-lib.org',
                    'Password': 'hhgu##$dfe__e',
                    'RememberMe': True}
            r = self._session.post('https://pdf-lib.org/account/admin', params)
            # 如果使用 request_verification_token 此处会出现 500 错误
            if r.status_code == 500:
                print(r.content.decode('utf-8'))
            print('Cookie is set  to:')
            print(r.cookies.get_dict())
            print('--------------------------------')
            print('Going to post article page..')
            r = self._session.get('https://pdf-lib.org/Manage/ArticleList', cookies=r.cookies)
            print(r.text)

    def request_verification_token(self):
        # 此处仍然会获取所需要的内容
        get_content = self._init_connection.init_connection('https://pdf-lib.org/account/admin')
        if get_content:
            try:
                get_token = get_content.find("input", {"name": "__RequestVerificationToken"}).get("value")
            except Exception as e:
                print(f"ot unhandled exception {e}")
                return False
            return get_token


if __name__ == '__main__':
    get_cookie = GetCookie()
    get_cookie.get_cookie_by_login()

上面的代码我们向登录页面发送相关参数,作用就是模拟我们输入用户名与密码登录页面。然后我们从请求中获取 cookie,打印登录结果。

对于简单的页面我们可以这样处理没有问题,但是如果网站比较复杂,他经常会暗自调整 cookie,或者如果我们一开始都不想使用 cookie,应该怎么处理呢?Requests 库的 session 函数可以完美的解决这些问题:

import requests
from bs4 import BeautifulSoup
from requests import Session, exceptions

from utils import connection_util

class GetCookie(object):
    def __init__(self):
        self._session = Session()
        self._init_connection = connection_util.ProcessConnection()

    def get_cookie_by_login(self):
        # 另外一个 session 中
        get_token=self.get_request_verification_token()
        if get_token:
            params = {'__RequestVerificationToken': get_token, 'Email': 'abc@pdf-lib.org',
                    'Password': 'hhgu##$dfe__e',
                    'RememberMe': True}
            r = self._session.post('https://pdf-lib.org/account/admin', params)
            # 如果使用 request_verification_token 此处会出现 500 错误
            if r.status_code == 500:
                print(r.content.decode('utf-8'))
            print('Cookie is set  to:')
            print(r.cookies.get_dict())
            print('--------------------------------')
            print('Going to post article page..')
            r = self._session.get('https://pdf-lib.org/Manage/ArticleList', cookies=r.cookies)
            print(r.text)

    def get_request_verification_token(self):
        # 连接网站
        try:
            headers = {
                "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.121 Safari/537.36",
                "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9"}
            html = self._session.get("https://pdf-lib.org/Account/Login", headers=headers)
        except (exceptions.ConnectionError, exceptions.HTTPError, exceptions.Timeout) as e:
            return False
        try:
            bsObj = BeautifulSoup(html.text, features='html.parser')
        except AttributeError as e:
            return False
        if bsObj:
            try:
                get_token = bsObj.find("input", {"name": "__RequestVerificationToken"}).get("value")
            except Exception as e:
                print(f"ot unhandled exception {e}")
                return False
            return get_token


if __name__ == '__main__':
    get_cookie = GetCookie()
    get_cookie.get_cookie_by_login()

在这个例子当中,会话(session)对象(调用 requests.Session() 获取)会继续跟踪会话信息,像 cookie,header,甚至是包括运行 HTTP 协议的信息,比如 HTTPAdapter(为 HTTP 和 HTTPS 的链接会话提供统一接口)。

Requests 是一个非常给力的库,程序员完全不用费脑子,也不用写代码,可能只是逊色于 Selenium,虽然写网络爬虫的时候,你可能像放手让 Requests 库替自己做所有的事情,但是持续关注 cookie 的状态,掌握它们可以控制的范围是非常重要的。这样可以避免痛苦的调试和追寻网站的异常,节省很多时间。

HTTP 基本接入认证

在发明 cookie 之前,处理网站登录最常用的方法就是使用 HTTP 基本接入认证(HTTP basic access authentication)。现在已经不常用了,但是我们偶尔还是会见到它,尤其是在一些安全性较高的网站或公司内部网站,以及一些 API 上使用。

下面是一个基本接入认证界面截图:

基本接入认证截图,用户必须输入用户名和密码才能登录

和前面的例子一样,需要输入用户名和密码才能登录。

Requests 库有一个 auth 模块专门用来处理 HTTP 认证:

import requests
from requests.auth import HTTPBasicAuth

def http_auth():
    auth = HTTPBasicAuth('user', '1223**%%adw')
    r = requests.post(url="http://www.test.com:3311/", auth=auth)
    print(r.text)

虽然这样看起来是一个普通的 POST 请求,但是有一个 HTTPBasicAuth 对象作为 auth 参数传递到请求当中。显示的结果将是用户名和密码验证成功的页面(如果验证失败,会是一个拒绝接入的页面)。

其他表单问题

表单是网络恶意机器人(malicious bots)酷爱的网站切入点。你当然不希望机器人创建垃圾账号,占用昂贵的服务器资源,或者在博客上提交垃圾评论。因此,新式的网站经常在 HTML 中使用很多安全措施,让表单不能被机器人批量提交数据。

关于验证码的详细内容我不打算在这篇文章中讲解(因为文章已经够长了),后面我将会介绍使用 Python 的图像处理和文本识别方法来详述这部分内容。

如果你在提交表单的时候遇到一些莫名其妙的错误,或者服务器一直以默认的请求为理由拒绝,我在以后的文章中在介绍这些问题。

本文所有的源代码已经托管于 Github 当中,地址:https://github.com/sycct/Scrape_1_1.git

如果有任何问题,欢迎大家 issue。

返回顶部