关于网络抓取的第一个建议是:不到迫不得已,不要进行网络抓取。
除了直接进行抓取之外,还有许多可以获取数据的方法。直接使用这些数据源对程序员和网站本身来说都能减少花销。很多情况下,如果直接进行抓取,就需要解析成千上万个页面的HTML。许多网站(比如谷歌和雅虎)都提供了核心服务的API,这样用户就可以不用抓取并解析原始的HTML了。
需要抓取的情况,比如,如果能够通过google搜索到需要的数据,但是google却没有提供可供下载的链接或APi,那就只能进行网络抓取了。此时需要牢记几条规则。首先,找一下要抓取的网站是否包含一个“服务条款”页面。然后,找找看有没有一个robot.txt文件,该文件会指出可以通过搜索引擎下载的URL以及禁止通过搜索引擎下载的URL。这样可以帮助我们避免下载到除了广告之外完全相同的内容,可以帮助网站控制负载。
如果遵循网站的服务条款和robots.txt,那么由于负载过高而导致IP被禁用的可能性也会大大降低。
在最普遍的情况下,抓取网站的过程需要使用我们前面学习的有关HTTP以及Web浏览器对HTTP的处理方式的内容。
import argparse,bs4,lxml.html,requests
from selenium import webdriver
from urllib.parse import urljoin
ROW = '{:>12} {}'
def download_page_with_requests(base):
session = requests.Session()
response = session.post(urljoin(base,'/login'),
{'username':'brandon','password':'pswd'})
assert response.url == urljoin(base,'/')
return response.text
def download_page_with_selenium(base):
browser= webdriver.Chrome()
browser.get(base)
assert browser.current_url == urljoin(base,'/login')
css = browser.find_element_by_css_selector
css('input[name="username"]').send_keys('')
css('input[name="password"]').send_keys('')
css('input[name="password"]').submit()
assert browser.current_url == urljoin(base,'/')
return browser.page_source
def scrape_with_soup(text):
soup = bs4.BeautifulSoup(text)
total = 0
for li in soup.find_all('li','to'):
dollars = int(li.get_text().split()[0].lstrip('$'))
memo = li.find('i').get_text()
total += dollars
print(ROW.format(dollars,memo))
print(ROW.format('-'* 8,'-' * 30))
print(ROW.format(total,'Total payments made'))
def scrape_with_lxml(text):
root = lxml.html.document_fromstring(text)
total = 0
for li in root.cssselect('li.to'):
dollars = int(li.text_content().split()[0].lstrip('$'))
memo = li.cssselect('i')[0].text_content()
total += dollars
print(ROW.format(dollars, memo))
print(ROW.format('-' * 8, '-' * 30))
print(ROW.format(total, 'Total payments made'))
def main():
url = 'http://127.0.0.1:5000/'
choice1 = input('Choose:\n\t1 to download page with selenium'
'\t2 to download with requests')
choice2 = input('Choose:\n\t3 to scrape with lxml'
'\t4 to scrape with soup\n\t')
if choice1 == 1 :
text = download_page_with_selenium(url)
else:
text = download_page_with_requests(url)
if choice2 == 3:
scrape_with_lxml(text)
else:
scrape_with_soup(text)
if __name__ == '__main__':
main()
使用requests和selenium的不同之处:
HTTP是专为万维网设计的。万维网通过超链接将海量文档连接起来,每个超链接都用URL来表示其指向的页面或页面中的某个小节。用户可以直接点击超链接来访问它所指向的页面。Python标准库也提供了用于解析及构造URL的方法。此外,还可以使用标准库提供的功能根据页面的基URL地址将相对URL转化为绝对URL。
Web应用程序通常会在对HTTP请求进行相应的服务器程序中连接持久化的数据存储(如数据库),然后构造作为响应的HTML。在这一过程中有一点是十分重要的,即应该使用数据库本身提供的功能来引用由Web外部传递来的不可信信息。也可以在Python中使用DB-API 2.0和任何ORM来正确地引用不可信信息。
Web框架各不相同,有的只提供最简单的功能,有的则提供了全栈式服务。如果使用简单的Web框架,就需要自己选择模板语言、ORM或是其他持久层方案。而全栈式的框架则内置了工具来提供这些功能。无论选择哪种框架,都可以在自己的代码中支持静态URL即/person/123/这样包含可变组件的URL。这些框架同样会提供生成与返回模板的方法,以及返回重定向信息或HTTP错误的功能。
每个网站编写者都会遇到一个大麻烦:在像Web这样一个复杂的系统中,组件之间的交互可能会适得用户违背了自己的操作本意,或是允许用户损害其他人的利益。在代码中设计与外部网络的接口时,一定要考虑跨站脚本攻击、跨站请求伪造以及对用户隐私攻击的可能性。在编写会从URL路径、URL查询字符串、POST请求或文件上传等途径接收数据的代码之前,一定要彻底理解这些安全威胁。
我们通常会在全栈式的框架以及轻量级的框架之间进行权衡。像Django这样的全栈式解决方案鼓励用户全部使用它所提供的刚弄工具,而它会为用户提供一个很不错的默认配置(比如自动提供表单的CSRF保护);而Flask这样的轻量级框架则要求我们自己选择其他工具,相互结合,形成最终的解决方案。此时我们就需要理解所有用到的组件。例如,如果选择Flask来开发应用程序,但是却不知道要提供CSRF保护,那么最后开发出的应用程序就无法抵御CSRF攻击了。
要抓取一个Web页面,就需要对网站的工作原理有透彻的理解,这样才能在脚本中模拟正常的用户交互——包括登录、填写以及提交表单这些复杂操作。在Python中,有很多方法可以用来获取和解析页面。目前,Requests和selenium是最流行的用来获取页面的库,而Beautiful Soup和lxml则是人们解析页面时最喜欢使用的方案。
《Python网络编程》第三版一共有18章,我的博客的学习过程将会只包括现在我们已经学习了的前11章,也就是关于网络编程基础以及HTTP的部分。12~18章不会出现在后面的博客中。它们分别是:
Ch 12 电子邮件的构造与解析
Ch 13 SMTP(简单邮件传输协议)
Ch 14 POP(邮局协议)
Ch 15 IMAP(Internet 消息访问协议)
Ch 16 telnet和SSH
Ch 17 FTP
Ch 18 RPC