python Web爬取工具总结 1 :Requests和BeautifulSoup

1. 引言

近一年接触了不少基于python的Web爬虫系统或工具库,收获不少,需要继续总结提高,所以下面对各类工具的应用方法和特性进行总结。

2. 内容概要

根据自己的学习经历,本文涉及内容大致有以下部分:
- Requests:常用于爬取单一或数量有限的网页,适合于爬取小规模、数据量小的,对爬取速度不敏感的内容。
- beautifuSoup:用于解析( HTML and XML files)文档。
- selinium
- urlib
- scrapy:常用于爬取网站,爬取对象的数据规模较大,对爬取速度敏感(必须赶上网站本身生成数据的速度)。
- pyspider
- pyquery
- 其他相关
目标定的挺大,下面一个个来了。

3. Requests库

官方网址:http://www.python-requests.org/en/master/
Requests库是唯一的面向python的“非转基因”http 库,支持python2.x和python3.x,但建议使用python3.5以上。http处理能力强,使用起来简洁有效。官方demo如下:

>>>import requests
>>> r = requests.get('https://api.github.com/user', auth=('user', 'pass'))
>>> r.status_code
200
>>> r.headers['content-type']
'application/json; charset=utf8'
>>> r.encoding
'utf-8'
>>> r.text
u'{"type":"User"...'
>>> r.json()
{u'private_gists': 419, u'total_private_repos': 77, ...}

Requests能简单地构造出HTTP/1.1 requests,不需要人工向URL中加入查询字串,或在POST数据中加入表单编码。借助urllib3(python3内置)TCP Keep-alive和HTTP连接池都是100%自动的。其他特性包括:

Keep-Alive & Connection Pooling
International Domains and URLs
Sessions with Cookie Persistence
Browser-style SSL Verification
Automatic Content Decoding
Basic/Digest Authentication
ElegantKey/Value Cookies
Unicode Response Bodies
Multipart File Uploads
Streaming Downloads
Connection Timeouts
Chunked Requests
.netrc Support
安装requests
安装方法很简单:pip install requests。
使用requests
使用requests库前,需要import requests。
初阶使用是很简单的,有许多中文教程。大多是参考官方quickstart(http://www.python-requests.org/en/master/user/quickstart/)的中文译本。
requests库里常用的有7个方法:
- get():获取html网页的主要方法,对应于http的get方法。
- put():向html网页提交put请求的方法,对应于http的put方法。
- delete():向html网页提交delete请求的方法,对应于http的delete。
- head():获取html网页头请求的方法,对应于http的HEAD。
- patch():向html网页提交局部修改请求的方法,对应于http的patch。
- post():向html网页提交POST请求,对应于http的post。
- request():构造一个请求,是支撑以上方法的基础方法。事实上,前6个方法都可以用request实现。
requests.request(method,url,**kwargs),其中method可以为‘GET’,’POST’,’HEAD’,’DELETE’,’PUT’,’PATCH’,’OPTION’, **kwargs有13个可选的控制访问的参数:
(1) params:字典或字节序列,作为参数增加到url中。例如:

kv = {'key1':'value1','key2':'value2'}
r = requests.request('GET','http://python.io/ws',params = kv)
print(r.url)
结果为:
https://www.python.org/ws?key2=value2&key1=value1

(2)data:字典、字节序列或文件对象,作为Request的内容。例如:

kv = {'key1':'value1','key2':'value2'}
r = requests.request('POST','http://python.io/ws',data = kv)
print(r.url)
#输出为https://www.python.org/ws,data不会将kv字典内容追加到url中,而是作为data参数,使用post方法传递。
body = '主题内容'
r = requests.request('POST','http://python.io/ws',data = body)
#也可以把字符序列作为请求参数。

(3)json:JSON型格式的数据,作为Request的内容。

kv = {'key1':'value1','key2':'value2'}
r = requests.request('POST','http://python.io/ws',json = kv)

(4)headers:字典,http头字段。使用它可以定制协议头。

kv = {'user-agent':'chrome/10'}
r = requests.request('POST','http://python.io/ws',headers = kv)

(5)cookies:字典或CookieJar,用于定制request中的cookie
(6)auth:元组,用于支持http认证功能。
(7)files:字典,用于传输文件。

#下面的程序中,首先fs作为打开文件内容构成的字典,然后作为reqeust的一部分发给url。
fs = {'file':open('data.xls','rb')}
r = requests.request('POST','http://python.io/ws',files = fs)

(8)timeout:设置超时时间,以秒为单位。
(9)proxies:字典,设定访问代理服务器,可以增加登陆认证,还可以用于隐藏源地址(服务器只会知道代理服务器地址)。

pxs = {'http':'http://user:[email protected]:1234','https':'https://10.10.10.1:4321',}
r = requests.request('POST','http://python.io/ws',proxies = pxs)

(10)allow_redirects:值为True或False,默认为True,是重定向允许与否的开关。
(11)stream:值为True或False,默认为True,获取内容立即下载开关。
(12)verify:值为True或False,默认为True,认证ssl证书的开关。
(13)cert:本地SSL证书路径。
由于网站一般限制向服务器提交数据,所以get方法是最常用的。由于网络访问的不确定性,所以常用下列代码范例:

import requests
def spider(url = url):
    try:
        url = url
        #为了防止服务器禁止爬虫爬取,可以用设置http头user-agent的方法,仿冒人使用浏览器访问。
        ua = {'User-agent':'Mozilla/5.0'} #Mozilla/5.0 可能是火狐、ie10、mozilla等。
        r = requests.get(url,timeout = 30,headers=ua)
        r.raise_for_status()#若响应中http 状态码不是200,则抛出异常。
        r.encoding = r.apparent_encoding
        return r.text
    except:
        return "Network access error."

下面写一个下载图片的例子:

import requests
import os
basdir = '~/Downloads/'
def getRemoteImage(imageurl):
    try:
        save_path = basdir + imageurl.split('/')[-1]

        ua = {'User-agent': 'Mozilla/5.0'}  # Mozilla/5.0 可能是火狐、ie10、mozilla等。
        r = requests.get(imageurl, timeout=30, headers=ua)
        r.raise_for_status()  # 若响应中http 状态码不是200,则抛出异常。
        if not os.path.exists(save_path):
            print("To save the remote image...")
            with open(save_path, 'wb') as f:
                f.write(r.content)
                f.close()
                print("Remote image file download successfully. ")
        else:
            print("Image file has existed.")
    except:
        print( "Network access error.")
if __name__ == "__main__":
    getRemoteImage(imageurl='http://img0.dili360.com/rw17/ga/M00/01/E5/wKgBzFQ2um-ACd7PAAa2w6jPYb4303.jpg')

深入学习requests库的高级应用,需要先了解requests中的几个关键类:Session,Request, Response等等,在爬取有SSL或CA的页面时还要有更多的细节需要了解。官方文档Advanced Usage(http://www.python-requests.org/en/master/user/advanced/#ssl-cert-verification)有详细介绍。

4.BeautifulSoup

(1)安装
使用pip安装的命令:pip install beautifulsoup
(2)使用
先考虑用beautifulsoup解析html。beautifulsoup会将html文档解析为树形模型,然后根据标签、属性等标识、提取、编辑信息。
先定义一段html文档:

html_doc = """
<html><head><title>The Dormouse's storytitle>head>
<body>
<p class="title"><b>The Dormouse's storyb>p>

<p class="story">Once upon a time there were three little sisters; and their names were
<a href="http://example.com/elsie" class="sister" id="link1">Elsiea>,
<a href="http://example.com/lacie" class="sister" id="link2">Laciea> and
<a href="http://example.com/tillie" class="sister" id="link3">Tilliea>;
and they lived at the bottom of a well.p>

<p class="story">...p>
"""

接下来,写使用beautifulsoup来解析上述文档的程序:

from bs4 import BeautifulSoup
soup = BeautifulSoup(html_doc, 'html.parser')
print(soup.prettify())

下面是执行后的输出结果:

# 
#  
#   </span>
<span class="hljs-preprocessor">#    The Dormouse's story</span>
<span class="hljs-preprocessor">#   
#  
#  
#   

# # The Dormouse's story # #

#

# Once upon a time there were three little sisters; and their names were # # Elsie # # , # # Lacie # # and # # Tillie # # ; and they lived at the bottom of a well. #

#

# ... #

# #

更多的操作:

soup.title
# <title>The Dormouse's storytitle>

soup.title.name
# u'title'

soup.title.string
# u'The Dormouse's story'

soup.title.parent.name
# u'head'

soup.p
# <p class="title"><b>The Dormouse's storyb>p>

soup.p['class']
# u'title'

soup.a
# <a class="sister" href="http://example.com/elsie" id="link1">Elsiea>

soup.find_all('a')
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsiea>,
#  <a class="sister" href="http://example.com/lacie" id="link2">Laciea>,
#  <a class="sister" href="http://example.com/tillie" id="link3">Tilliea>]

soup.find(id="link3")
# <a class="sister" href="http://example.com/tillie" id="link3">Tilliea>

为了获取html中的超链接,可以如下操作:

for link in soup.find_all('a'):
    print(link.get('href'))
输出为:
# http://example.com/elsie
# http://example.com/lacie
# http://example.com/tillie

输出全部文本的方法如下:

print(soup.get_text())
# The Dormouse's story
#
# The Dormouse's story
#
# Once upon a time there were three little sisters; and their names were
# Elsie,
# Lacie and
# Tillie;
# and they lived at the bottom of a well.
#
# ...

beautifulsoup在处理html或xml文档时,有5种基本元素:标签Tag、Tag的Name、属性Attributes、NavigableString、Comment。
遍历标签的方法有:contents、childredn、descendants、parent、parents、next_sibling、previous_sibling、next_siblings、previous_siblings、find_all等方法。find_all是比较常用且基础的一个。

find_all( name , attrs , recursive , text , **kwargs )
find_all() 方法搜索当前tag的所有tag子节点,并判断是否符合过滤器的条件.这里有几个例子:

#例子
soup.find_all("title")
# [The Dormouse's story]
soup.find_all("p", "title")
# [

"title">The Dormouse's story

] soup.find_all("a") # [<a class="sister" href="http://example.com/elsie" id="link1">Elsiea>, # <a class="sister" href="http://example.com/lacie" id="link2">Laciea>, # <a class="sister" href="http://example.com/tillie" id="link3">Tilliea>] soup.find_all(id="link2") # [<a class="sister" href="http://example.com/lacie" id="link2">Laciea>] import re soup.find(text=re.compile("sisters")) # u'Once upon a time there were three little sisters; and their names were\n'

find_all中的参数:

  • name 参数可以查找所有名字为 name 的tag,字符串对象会被自动忽略掉。注意: 搜索 name 参数的值可以使任一类型的 过滤器 ,字符串,正则表达式,列表,方法或是 True 。
#简单的用法如下:
soup.find_all("title")
# [The Dormouse's story]
  • keyword 参数,如果一个指定名字的参数不是搜索内置的参数名,搜索时会把该参数当作指定名字tag的属性来搜索,如果包含一个名字为 id 的参数,Beautiful Soup会搜索每个tag的”id”属性.
soup.find_all(id='link2')
# ["sister" href="http://example.com/lacie" id="link2">Lacie]

如果传入 href 参数,Beautiful Soup会搜索每个tag的”href”属性:

soup.find_all(href=re.compile("elsie"))
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsiea>]

搜索指定名字的属性时可以使用的参数值包括 字符串 , 正则表达式 , 列表, True 。
下面的例子在文档树中查找所有包含 id 属性的tag,无论 id 的值是什么:

soup.find_all(id=True)
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsiea>,
#  <a class="sister" href="http://example.com/lacie" id="link2">Laciea>,
#  <a class="sister" href="http://example.com/tillie" id="link3">Tilliea>]

使用多个指定名字的参数可以同时过滤tag的多个属性:

soup.find_all(href=re.compile("elsie"), id='link1')
# ["sister" href="http://example.com/elsie" id="link1">three]

有些tag属性在搜索不能使用,比如HTML5中的 data-* 属性:

data_soup = BeautifulSoup('
data-foo="value">foo!
') data_soup.find_all(data-foo="value") # SyntaxError: keyword can't be an expression

但是可以通过 find_all() 方法的 attrs 参数定义一个字典参数来搜索包含特殊属性的tag:

data_soup.find_all(attrs={"data-foo": "value"})
# [
"value">foo!
]

按CSS搜索
按照CSS类名搜索tag的功能非常实用,但标识CSS类名的关键字 class 在Python中是保留字,使用 class 做参数会导致语法错误.从Beautiful Soup的4.1.1版本开始,可以通过 class_ 参数搜索有指定CSS类名的tag:

soup.find_all("a", class_="sister")
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsiea>,
#  <a class="sister" href="http://example.com/lacie" id="link2">Laciea>,
#  <a class="sister" href="http://example.com/tillie" id="link3">Tilliea>]
  • class_ 参数,同样接受不同类型的 过滤器 ,字符串,正则表达式,方法或 True :
soup.find_all(class_=re.compile("itl"))
# [

The Dormouse's story

]
def has_six_characters(css_class): return css_class is not None and len(css_class) == 6 soup.find_all(class_=has_six_characters) # [Elsie, # Lacie, # Tillie]

tag的 class 属性是 多值属性 .按照CSS类名搜索tag时,可以分别搜索tag中的每个CSS类名:

css_soup = BeautifulSoup('

'
) css_soup.find_all("p", class_="strikeout") # [

]
css_soup.find_all("p", class_="body") # [

]

搜索 class 属性时也可以通过CSS值完全匹配:

css_soup.find_all("p", class_="body strikeout")
# [

]

完全匹配 class 的值时,如果CSS类名的顺序与实际不符,将搜索不到结果:

soup.find_all("a", attrs={"class": "sister"})
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsiea>,
#  <a class="sister" href="http://example.com/lacie" id="link2">Laciea>,
#  <a class="sister" href="http://example.com/tillie" id="link3">Tilliea>]

text 参数
通过 text 参数可以搜搜文档中的字符串内容.与 name 参数的可选值一样, text 参数接受 字符串 , 正则表达式 , 列表, True . 看例子:

soup.find_all(text="Elsie")
# [u'Elsie']

soup.find_all(text=["Tillie", "Elsie", "Lacie"])
# [u'Elsie', u'Lacie', u'Tillie']

soup.find_all(text=re.compile("Dormouse"))
[u"The Dormouse's story", u"The Dormouse's story"]

def is_the_only_string_within_a_tag(s):
    ""Return True if this string is the only child of its parent tag.""
    return (s == s.parent.string)

soup.find_all(text=is_the_only_string_within_a_tag)
# [u"The Dormouse's story", u"The Dormouse's story", u'Elsie', u'Lacie', u'Tillie', u'...']

虽然 text 参数用于搜索字符串,还可以与其它参数混合使用来过滤tag.Beautiful Soup会找到 .string 方法与 text 参数值相符的tag.下面代码用来搜索内容里面包含“Elsie”的标签:

soup.find_all("a", text="Elsie")
# [<a href="http://example.com/elsie" class="sister" id="link1">Elsiea>]

limit 参数

find_all() 方法返回全部的搜索结构,如果文档树很大那么搜索会很慢.如果我们不需要全部结果,可以使用 limit 参数限制返回结果的数量.效果与SQL中的limit关键字类似,当搜索到的结果数量达到 limit 的限制时,就停止搜索返回结果.

文档树中有3个tag符合搜索条件,但结果只返回了2个,因为我们限制了返回数量:

soup.find_all("a", limit=2)
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsiea>,
#  <a class="sister" href="http://example.com/lacie" id="link2">Laciea>]

recursive 参数

调用tag的 find_all() 方法时,Beautiful Soup会检索当前tag的所有子孙节点,如果只想搜索tag的直接子节点,可以使用参数 recursive=False .

一段简单的文档:




The Dormouse’s story


是否使用 recursive 参数的搜索结果:

soup.html.find_all("title")
# [The Dormouse's story]

soup.html.find_all("title", recursive=False)
# []

像调用 find_all() 一样调用tag

find_all() 几乎是Beautiful Soup中最常用的搜索方法,所以我们定义了它的简写方法. BeautifulSoup 对象和 tag 对象可以被当作一个方法来使用,这个方法的执行结果与调用这个对象的 find_all() 方法相同,下面两行代码是等价的:

soup.find_all("a")
soup("a")

这两行代码也是等价的:

soup.title.find_all(text=True)
soup.title(text=True)

附录A:网络爬虫的限制

  1. 来源审查
    检查来访http协议头的User-agent域,只响应浏览器或友好爬虫的访问。
    2.发布公告
    告知所有爬虫,即Robots.txt.
    Robots Exclusion Standard协议用来公告网络爬虫,不允许爬取哪些信息。例如:
##https://www.jd.com/robots.txt
User-agent: * 
Disallow: /?* 
Disallow: /pop/*.html 
Disallow: /pinpai/*.html?* 
User-agent: EtaoSpider 
Disallow: / 
User-agent: HuihuiSpider 
Disallow: / 
User-agent: GwdangSpider 
Disallow: / 
User-agent: WochachaSpider 
Disallow: /

附录B:三种信息标记的形式

为了描述信息,有三种信息标记的形式:xml、json、yaml,使用他们来描述会使信息变得有结构。
1. xml
xml指可扩展标记语言,XML被设计用来传输和存储数据。它通过标签和属性来描述信息;
xml是最早的通用信息标记,扩展性好、通用性好,但繁琐。
常用于internet上信息交互。
2. json
JSON是JavaScript 对象表示法(JavaScript Object Notation),它也用于存储和交换文本信息。JSON类似 XML,但JSON 比 XML 更小、更快,更易解析。json使用键值对来描述信息。
json采用的是有类型的键值对。适合于程序处理(js),较xml简洁。
通常使用在移动应用云端和节点间的通信,但无法表达注释。
3. yaml
YAML是“另一种标记语言”的外语缩写, 它的设计目标,就是方便人类读写。它实质上是一种通用的数据串行化格式。它的基本语法规则如下:“大小写敏感、使用缩进表示层级关系、缩进时不允许使用Tab键,只允许使用空格、缩进的空格数目不重要,只要相同层级的元素左侧对齐即可。”
yaml采用的是无类型的键值对。文本信息比例最高,可读性好。常用于各类配置文件。
YAML 支持的数据结构有三种:
- 对象:键值对的集合,又称为映射(mapping)/ 哈希(hashes) / 字典(dictionary)
- 数组:一组按次序排列的值,又称为序列(sequence) / 列表(list)
- 纯量(scalars):单个的、不可再分的值

  1. 信息提取的一般方法

    方法一:完整解析信息的标记形式,在提取关键信息。
    需要标记解析器,例如:beautifulsoup的标签树遍历方式。
    优点:信息解析准确
    缺点:提取过程繁琐,速度慢。

方法二:无视信息标记形式,直接搜索关键信息。
需要全文搜索,对信息文本的查找函数。
优点:提取过程简洁、速度极快
缺点:提取结果准确性与信息内容相关。

方法三:融合方法1和方法2.

你可能感兴趣的:(python开发,爬虫技术)