第九章、通过表单和登录抓取
当你跨过了网络爬虫的基础知识第一个问题就来了:“我如何访问登录界面后面的信息?”网络是日益增长的社交媒体和用户生成内容的相互作用。表单和登录是这类型网站的一个几乎无法避免的组成部分,幸运的是,他们也比较容易处理。
到目前为止,我们爬虫中大多数的与服务器交互的例子只包括使用HTTP的GET方法请求信息。在本章中,我们将重点放在给web服务器推送信息进行存储和分析的POST方法上。
表单基本上给用户一种给web服务器提交可以理解和使用的POST请求的方式。就像一个网站上的链接标签帮助用户格式化GET请求,HTML表单帮助他们格式化POST请求。当然,用一点点的代码,就有可能用爬虫自己简单的创建这些请求并提交。
Python的Request库
尽管可以仅使用Python的核心库来浏览网页表单,有时候一点语法糖可以是生活更甜。当你开始用urllib开始做一些基础GET请求之外的事情,它是Python的核心库之外可以帮助你的。
这个Request库是在处理复杂的HTTP请求,cookie,headers等等是很棒的。
以下是Request创建者Kenneth Reitz对Python的核心工具的看法:
Python的标准模块urllib2提供你所需要的大多数HTTP功能,但是API被彻底破坏。它支持的是不同时间——和不同的网页。它需要大量的执行简单任务的工作(甚至方法重载)。
在Python中事情不应该是这个样子。
就像Python的其他库,这个Request库可以通过任何第三方Python库管理器安装,如pip或者通过下载源文件安装。
提交一个基本表单
大多数的web表单包含一些HTML字段,一个提交按钮和一个“行动”页面上完成实际的表单处理。HTML字段通常包含文本,但也可能包含一个文件上传或者一些其他非文本内容。
大多数受欢迎的网站在他们的robot.txt文件中阻止想要访问它们的登录表单(附录C讨论抓取这种表单的合法性),所以为了保证其安全可靠我在pythonscraping.com构建了一系列不同类型的表单和登录,你可以运行你的爬虫来对抗。这些最基本形式的表单在http://bit.ly/1AGKPRU。
完整的表单是:
<form method="post" action="processing.php">
First name: <input type="text" name="firstname"><br>
Last name: <input type="text" name="lastname"><br>
<input type="submit" value="Submit">
</form>
这里有两点事情需要注意:第一,两个输入字段的名称是:firstname和lastname。这很重要。这些名称字段确定了当表单提交时通过POST到服务器时的可变参数名称。如果你想要模仿表单的动作POST你的数据,你需要确保你的变量名匹配。
第二个需要注意的是表单的动作实际上是processing.php(绝对路径是http://bit.ly/1d7TPVk)。任何POST请求表单都在此页面产生,而不是表单本身在的页面。请记住:HTML表单的目的仅仅是帮助网站访问者格式正确发送到页面的请求,不是真正的执行。除非你是研究格式化请求本身,否则不需要在可以找到表单的页面打扰太多。
通过Request库提交表单可以用四行来完成,其中包括导入和打印指令(是的就是这么简单):
import request
params = {'firstname':'Ryan', 'lastname':'Mitchell'}
r = request.post("http://pythonscraping.com/files/processing.php", data=params)
print(r.text)
提交表单之后,脚本应该返回页面的内容:
Hello there, Ryan Mitchell!
这个脚本可以应用于互联网上遇到的许多简单的表单。例如,报名参加O'Reilly Media的简报表单如下所示:
<form action="http://post.oreilly.com/client/o/oreilly/forms/quicksignup.cgi"
id="example_form2" method="POST">
<input name="client_token" type="hidden" value="oreilly" />
<input name="subscribe" type="hidden" value="optin" />
<input name="success_url" type="hidden" value="http://oreilly.com/store/
newsletter-thankyou.html" />
<input name="error_url" type="hidden" value="http://oreilly.com/store/
newsletter-signup-error.html" />
<input name="topic_or_dod" type="hidden" value="1" />
<input name="source" type="hidden" value="orm-home-t1-dotd" />
<fieldset>
<input class="email_address long" maxlength="200" name=
"email_addr" size="25" type="text" value=
"Enter your email here" />
<button alt="Join" class="skinny" name="submit" οnclick=
"return addClickTracking('orm','ebook','rightrail','dod'
);" value="submit">Join</button>
</fieldset>
</form>
尽管它第一眼看起来令人生畏,记住,在大多数情况下(我们随后讨论例外),你只是在寻找两个东西:
①你想把数据提交给的字段(一个或者多个)(在这种情况下,该名称是email_address)。
②表单本身的action属性,即表单实际推送到的页面。(在这种情况下是, http://post.oreilly.com/client/o/oreilly/forms/quicksignup.cgi)
只需要添加到请求信息中并运行它:
import request
params = {'email_addr':'ryan.e.mitchell@gmail.com'}
r = request.post("http://post.oreilly.com/client/o/oreilly/forms/
quicksignup.cgi", data=params)
print(r.text)
在这种情况下,网页返回的是其他简单表单来填写,在你真正的把它放入O'Reilly的邮件列表之前,但这种相同的概念可以应用于表单。不过,我建议你很好的使用你的权利,而不是出版商无效注册的垃圾邮件,如果你想在家里尝试。
单选按钮,复选框和其他输入
很显然,不是所有的网页表单都是文本字段集合后面跟一个提交按钮。标准的HTML包含多种可能的表单输入字段:单选按钮,复选按钮和选择框,仅举几例。在HTML5中,添加了滑块(范围输入字段),电子邮件,日期等等。通过自定义的JavaScript字段的可能性是无止境的,有colorpickers,日历,和其他开发者拿出的下一个东西。
无论任何一种看似复杂的表单字段,你只需要担心两件事:元素的名称以及它的值。该元素的名称可以很容易的通过查看源代码并且找打名字的属性确定。该值有时候是棘手的,因为它可能在表单提交之前由JavaScript瞬时填充。Colorpickers作为一个相当奇特的表单的例子,将可能有类似#F03030的值。
如果你不确定输入字段值的格式,有一些工具,你可以使用跟踪你从浏览器发送的GET请求和从网站来的POST请求。向前面说的,也许最好最明显的方法来追踪GET请求,是简单的看一个网站的网址。如果URL是这样的:
hhtp://domainname.com?thing1=foo&thing2=bar
你知道这对应这样一种表单类型:
<form method="GET" action="someProcessor.php">
<input type="someCrazyInputType" name="thing1" value="foo" />
<input type="anotherCrazyInputType" name="thing2" value="bar"/>
<input type="submit" value="Submit"/>
</form>
对应的Python参数对象:
{'thing1':'foo', 'thing2':'bar'}
你可以在图9-1中看到这个。
如果你坚持一个复杂的前瞻性的POST形式,并且是要看看你的浏览器发送到服务器的到底是哪些参数,最简单的方法是使用浏览器检测器或开发工具来查看它们。
谷歌开发人员工具,可以通过菜单转到访问→查看→开发工具。他提供了你的浏览器和当前网站进行交互的所有查询的列表,是一个很好的方式来查看这些查询的详细组成。
提交文件和图片
尽管文件上传在互联网中是常见的,但是文件上传在网页抓取中是不经常使用的。然而,有可能的是你可能要编写一个自己网站的测试,涉及文件上传。无论如何,这是一个你知道该怎么做是很有用的。
这里有一个练习文件上传在http://pythonscraping/files/form2.html。在这个页面的表单有如下标记:
<form action="processing2.php" method="post" enctype="multipart/form-data">
Submit a jpg, png, or gif: <input type="file" name="image"><br>
<input type="submit" value="Upload File">
</form>
除了<input>标签有类型属性file,他看起来和前面使用的基于文本形式的表单基本相同。幸运的是,表单使用Python的Request库的方式也很相似:
import request
file = {'upload':open('../files/Python-logo.png', 'rb')}
r = request.post("http://pythonscraping.com/pages/processing2.php", files=files)
print(r.text)
请注意,提交表单字段的值,现在是一个由open函数返回的Python文件对象替代了简单的字符串。在这个例子中,我提交了一个存储在我本地机器的图像文件,在路径../files/Python-logo.png,相对于Python脚本运行的地方。
是的,就是这么简单!
处理登录和Cookies
到目前为止,我们主要讨论了让你提交信息给网站的表单和在网页的表单之后立即查看需要的信息。这和登录表单有什么不同?登录表单可以让你在浏览网站的过程中存在一个永久性的“登录”状态。
大多数现代网站使用Cookie来跟踪谁是登录谁不是。一旦网站验证其存储在浏览器cookie中的登录凭据,cookie通常包含服务器生成的令牌,超时和跟踪信息。该网站然后使用这个cookie作为一种验证证明,将会展示给在网站上所有时间你访问的页面。在未广泛使用cookie的90年代中期以前,对于网站来说保持用户安全验证和跟踪他们是一个很大的问题。
尽管cookie对于网页开发者是一个很好的解决方案,它们可以给网页爬虫造成问题。你可以整天提交表单,但是你不保持跟踪表单之后发回给你的cookie,下一个你将访问的页面将视为你从来没有登录。
我在http://bit.ly/1KwvSSG创建了一个简单的登录表单(用户名可以是任何东西,但是密码必须是'password')。
这个表单在http://bit.ly/1d7U2I1被处理,并且包含一个到“主网站”页面的链接http://bit.ly/1JcansT。
如果你第一时间没有登录就尝试访问欢迎页面或者个人资料页,你将会得到一个错误和一个继续之前先登录的指示。在文件页面,将会检查你浏览器的cookie来查看它的cookie是否在登录页面被设置。
使用Request库跟踪cookie很容易:
import request
params = {'username':'Ryan', 'password':'password'}
r = request.post("http://pythonscraping.com/pages/cookies/welcome.php", params)
print('Cookie is set to: ')
print(r.cookies.get_dict())
print('------------------')
print('Going to profile page...')
r = request.get("http://pythonscraping.com/pages/cookies/profile.php", cookies=r.cookies)
print(r.text)
在这里我发送登录参数到充当登录表单处理器的欢迎页面。我检索cookie结果中的最后一个请求,打印出结果核查,然后将他们设置cookie参数之后发送到个人资料页面。
这非常适用于简单的情况,但如果你想处理一个更复杂的网站,经常没有警告的修改cookie,或者你想都别想关于cookie的开始?在这个例子中Request的session(会话控制)函数工作的很完美:
import request
session = requests.Session()
params = {'username':'username', 'password':'password'}
s = session.post("http://pythonscraping.com/pages/cookies/welcome.php", params)
print("Cookie is set to:")
print(s.cookie.get_dict())
print('-----------------')
print("Going to profile page...")
s = session.get("http://pythonscraping.com/pages/cookies/profile.php")
print(s.text)
在这种情况下,会话控制对象(通过调用requests.Session()检索)跟踪会话信息,比如cookie,header以及有关的协议信息,甚至可能你在HTTP上面运行的比如HTTPAdapters。
Request是一个奇妙的库,次于它的也许只有Selenium(我们将在第十章讲到),它的处理完整性使得程序员不必考虑它以及编写代码本身。尽管它可能尝试让库去做所有的事情,让事情变得更简单。极其重要的是不知道cookie的样子。它们控制者你写网络爬虫。他可以节省很多痛苦的调试时间或者搞清楚一个网站为什么有怪异的行为!
HTTP基本接入认证(HTTP Basic Access Authentication)
cookie出现之前,人们普遍的处理登录方式是用HTTP基本接入认证。你仍然可以时不时的看到它,特别是在一些高安全性或者企业站点的一些API。我在http://pythonscraping.com/pages/auth/login.php页面创建了一个这种类型的身份认证。如图9-2:
像往常的例子一样,你可以使用任意的用户名,但是密码必须是“password”。
Request包中包含一个专门处理HTTP认证的身份验证模块(auth(Authentication) module):
import request
from request.auth import AuthBase
from request.auth import HTTPBasicAuth
auth = HTTPBasicAuth('ryan', 'password')
r = request.post(url="http://pythonscraping.com/pages/auth/login.php", auth=auth)
print(r.text)
尽管这似乎是一个普通的POST请求,HTTPBasicAuthor对象被作为auth参数在request中传递。所得到文本结果将是通过用户名和密码保护的页面(如果请求失败,拒绝访问页面)。
表单的其他问题
web表单是恶意机器人进入的一个热点。你不希望僵尸用户创建账户,占用宝贵的服务器处理时间,或者在一个博客上提交垃圾评论。处于这个原因,现代网页中经常有许多安全特征并入了HTML表单,可能不会立即显示出来。
验证码问题,查看第十一章,涵盖了Python中的图像处理和文字识别。
如果你遇到一个神秘的错误,或者服务器处于一个未知的原因拒绝你的表单,查看第十二章,涵盖了蜜罐,隐藏字段以及网站保护他们表单的其他措施。