内容字号:默认大号超大号

段落设置:段首缩进取消段首缩进

字体设置:切换到微软雅黑切换到宋体

Python Spider详解

2018-07-10 17:45 出处:清屏网 人气: 评论(0

By Tank Li

1 爬虫基础

1.1 什么是网络爬虫?

网络爬虫(又被称为网页蜘蛛,网络机器人),是一种按照一定的规则,自动的抓取万维网信息的程序或者脚本。所谓Python爬虫就是利用Python语言编写的爬虫脚本。在用户浏览网页的过程中,我们可能会看到文字图片视频等等丰富的网页元素,比如在浏览器打开 https://www.google.com ,我们会看到几张的图片以及搜索框,这个过程其实就是用户输入网址之后,经过DNS服务器,找到服务器主机,向服务器发出一个请求,服务器经过解析之后,发送给用户的浏览器 HTML、JS、CSS 等文件,浏览器渲染出来,用户便可以看到形形色色的网页了。因此,用户看到的网页实质是由 HTML 代码构成的,爬虫要爬的是这些内容,通过分析和过滤这些 HTML 代码,实现对图片、文字等资源的获取。

1.2 Http协议简介

所有的web请求都是通过Http协议进行通信,我们写爬虫就是模仿浏览器和web服务器之间的通信。因此我们很有必要对HTTP协议有一定的了解。关于Http协议,可以参考之前写的一篇 文章 第二节。这里推荐比较好用的Http分析工具。

  • Chrome
  • Firefox
  • Fiddler (专业的网络抓包工具)

下面我们来抓Http包,以访问brioncd为例,我们在浏览器输入htttp://brioncd.amsl.com, Request为

Host: brioncd.asml.com
Proxy-Connection: keep-alive
Cache-Control: max-age=0
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36
Upgrade-Insecure-Requests: 1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9,zh;q=0.8,zh-CN;q=0.7
Cookie: sessionid=92e7c780b58ce84b92767f6f43932d9a

服务器返回的Reponse

HTTP/1.1 200 OK
Date: Wed, 10 Jan 2018 07:46:13 GMT
Server: Apache/2.2.15 (Red Hat)
Vary: Cookie
Connection: close
Transfer-Encoding: chunked
Content-Type: text/html; charset=utf-8
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<!-- base_content.html -->
   < head >
     < title >Core Dump: Brion Q&amp;A Forum</ title >
     < meta name = "description" content = "Ask and answer questions." >
     < meta name = "keywords" content = "Coredump,CNPROG,forum,community" >
.......

对于POST请求,我们以登入brioncd为例, Request为

Host: brioncd.asml.com
Proxy-Connection: keep-alive
Content-Length: 62
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9,zh;q=0.8,zh-CN;q=0.7
Cookie: sessionid=6b34e27447710737ee9598ae3ee036d7
username=tanli&password=123456&blogin=Login&validate_email=yes

Reponse为

HTTP/1.1 302 FOUND
Date: Wed, 10 Jan 2018 08:23:56 GMT
Server: Apache/2.2.15 (Red Hat)
Vary: Cookie
Set-Cookie: sessionid=b8e4623e735cadbc1d01f26c744bdf30; expires=Wed, 24-Jan-2018 08:23:56 GMT; Max-Age=1209600; Path=/
Content-Length: 0
Connection: close
Content-Type: text/html; charset=utf-8
 

1.3 urllib库的基本使用

我们用python基本库urllib2库抓一个网页,很简单只要调用urlopen函数,函数原型为

urllib2.urlopen(url[, data][, timeout])
  • 第一个参数url为网络地址
  • 第二个参数data是访问URL时要传送的数据
  • 第三个timeout是设置超时时间

第二三个参数是可以不传送的,data默认为空None,timeout默认为socket._GLOBAL_DEFAULT_TIMEOUT

# -- crawl a web page by urlopen
response = urllib2.urlopen( "http://brioncd.asml.com" )
print response.read()

或者先构造Request再调用urlopen

# -- init a request and crawl a web page by urlopen
request = urllib2.Request( "http://brioncd.asml.com" )
response = urllib2.urlopen(request)
print response.read()

为了方便调试http通信过程,查看http包,urllib可以设置显示调试信息

# -- set debug log
httpHandler = urllib2.HTTPHandler(debuglevel = 1 )
httpsHandler = urllib2.HTTPSHandler(debuglevel = 1 )
opener = urllib2.build_opener(httpHandler, httpsHandler)
urllib2.install_opener(opener)

1.4 Get和Post方法数据传送

上面的程序演示了最基本的网页抓取,不过,现在大多数网站都是动态网页,需要你动态地传递参数给它,它做出对应的响应。所以,在访问时,我们需要传递数据给它。

数据传送分为POST和GET两种方式,两种方式有什么区别呢?最重要的区别是GET方式是直接以链接形式访问,链接中包含了所有的参数,当然如果包含了密码的话是一种不安全的选择,不过你可以直观地看到自己提交了什么内容。POST则不会在网址上显示所有的参数,不过如果你想直接查看提交了什么就不太方便了,大家可以酌情选择。

# -- get request by urlopen
data = { 'sort' : 'mostvoted' ,
     'pagesize' : '15' }
data = urllib.urlencode(data)
geturl = url + "?" + data
print geturl
request = urllib2.Request(geturl)
response = urllib2.urlopen(request)
print response.read()
#-- post request by urlopen
values = { 'username' : 'tanli' ,
    'password' : '123456' ,
    'blogin' : 'Login' ,
    'validate_email' : 'yes' }
data = urllib.urlencode(values)
request = urllib2.Request(url, data)
response = urllib2.urlopen(request)
print response.read()

1.5 设置header

有些网站不会同意程序直接用上面的方式进行访问,服务器会对请求进行识别,如果发现有问题,那么站点根本不会响应,所以为了完全模拟浏览器的工作,我们需要设置一些Headers 的属性。

# -- get request with header by urlopen
data = { 'sort' : 'mostvoted' ,
     'pagesize' : '15' }
data = urllib.urlencode(data)
headers = {
   'User-Agent' : 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36' ,
   'Referer' : 'http://brioncd.asml.com' }
geturl = url + "?" + data
request = urllib2.Request(geturl, headers = headers)
response = urllib2.urlopen(request)
print response.read()

1.6 异常处理

调用urlopen抓取网页主要有两种异常URLError还有HTTPError,以及对它们的一些处理。首先解释下URLError可能产生的原因:

  • 网络无连接,即本机无法上网
  • 连接不到特定的服务器
  • 服务器不存在

# -- exception handler
request = urllib2.Request( "http://brioncd1.asml.com" )
try :
   response = urllib2.urlopen(request)
except urllib2.HTTPError, e:
   print e.code, e.reason
except urllib2.URLError, e:
   print e.reason

HTTPError是URLError的子类,在你利用urlopen方法发出一个请求时,服务器上都会对应一个应答对象response,其中它包含一个数字”状态码”。举个例子,假如response是一个”重定向”,需定位到别的地址获取文档,urllib2将对此进行处理。其他不能处理的,urlopen会产生一个HTTPError,对应相应的状态吗,HTTP状态码表示HTTP协议所返回的响应的状态。下面将状态码归结如下:

100:继续 客户端应当继续发送请求。客户端应当继续发送请求的剩余部分,或者如果请求已经完成,忽略这个响应。
101: 转换协议 在发送完这个响应最后的空行后,服务器将会切换到在Upgrade 消息头中定义的那些协议。只有在切换新的协议更有好处的时候才应该采取类似措施。
102:继续处理 由WebDAV(RFC 2518)扩展的状态码,代表处理将被继续执行。
200:请求成功 处理方式:获得响应的内容,进行处理
201:请求完成,结果是创建了新资源。新创建资源的URI可在响应的实体中得到 处理方式:爬虫中不会遇到
202:请求被接受,但处理尚未完成 处理方式:阻塞等待
204:服务器端已经实现了请求,但是没有返回新的信 息。如果客户是用户代理,则无须为此更新自身的文档视图。 处理方式:丢弃
300:该状态码不被HTTP/1.0的应用程序直接使用, 只是作为3XX类型回应的默认解释。存在多个可用的被请求资源。 处理方式:若程序中能够处理,则进行进一步处理,如果程序中不能处理,则丢弃
301:请求到的资源都会分配一个永久的URL,这样就可以在将来通过该URL来访问此资源 处理方式:重定向到分配的URL
302:请求到的资源在一个不同的URL处临时保存 处理方式:重定向到临时的URL
304:请求的资源未更新 处理方式:丢弃
400:非法请求 处理方式:丢弃
401:未授权 处理方式:丢弃
403:禁止 处理方式:丢弃
404:没有找到 处理方式:丢弃
500:服务器内部错误 服务器遇到了一个未曾预料的状况,导致了它无法完成对请求的处理。一般来说,这个问题都会在服务器端的源代码出现错误时出现。
501:服务器无法识别 服务器不支持当前请求所需要的某个功能。当服务器无法识别请求的方法,并且无法支持其对任何资源的请求。
502:错误网关 作为网关或者代理工作的服务器尝试执行请求时,从上游服务器接收到无效的响应。
503:服务出错 由于临时的服务器维护或者过载,服务器当前无法处理请求。这个状况是临时的,并且将在一段时间以后恢复。

HTTPError实例产生后会有一个code属性,这就是是服务器发送的相关错误号。因为urllib2可以为你处理重定向,也就是3开头的代号可以被处理,并且100-299范围的号码指示成功,所以你只能看到400-599的错误号码。

1.7 Cookie使用

接下来我们一起来看一下Cookie的使用,什么是Cookie?

A cookie, also known as an HTTP cookie, web cookie, or browser cookie, is a small piece of data sent from a website and stored in a user’s web browser while a user is browsing a website. When the user browses the same website in the future, the data stored in the cookie can be retrieved by the website to notify the website of the user’s previous activity. Cookies were designed to be a reliable mechanism for websites to remember the state of the website or activity the user had taken in the past. This can include clicking particular buttons, logging in, or a record of which pages were visited by the user even months or years ago.
— from wikipedia

为什么要使用Cookie呢?Cookie,指某些网站为了辨别用户身份、进行session跟踪而储存在用户本地终端上的数据(通常经过加密)。比如说有些网站需要登录后才能访问某个页面,在登录之前,你想抓取某个页面内容是不允许的。那么我们可以利用Urllib2库保存我们登录的Cookie,然后再抓取其他页面就达到目的了。

在此之前呢,我们必须先介绍一个opener的概念。当你获取一个URL你使用一个opener(一个urllib2.OpenerDirector的实例。在前面,我们都是使用的默认的opener,也就是urlopen。它是一个特殊的opener,可以理解成opener的一个特殊实例,传入的参数仅仅是url,data,timeout。如果我们需要用到Cookie,只用这个opener是不能达到目的的,所以我们需要创建更一般的opener来实现对Cookie的设置。

cookielib模块的主要作用是提供可存储cookie的对象,以便于与urllib2模块配合使用来访问Internet资源。Cookielib模块非常强大,我们可以利用本模块的CookieJar类的对象来捕获cookie并在后续连接请求时重新发送,比如可以实现模拟登录功能。该模块主要的对象有CookieJar、FileCookieJar、MozillaCookieJar、LWPCookieJar。

它们的关系

1.7.1 cookie保存到变量

我们先利用CookieJar对象实现获取cookie的功能,存储到变量中。

# -- cookie save to variable
httpHandler = urllib2.HTTPHandler(debuglevel = 1 )
httpsHandler = urllib2.HTTPSHandler(debuglevel = 1 )
#声明一个CookieJar对象实例来保存cookie
cookie = cookielib.CookieJar()
#利用urllib2库的HTTPCookieProcessor对象来创建cookie处理器
handler = urllib2.HTTPCookieProcessor(cookie)
#通过handler来构建opener
opener = urllib2.build_opener(httpHandler, httpsHandler, handler)
#此处的open方法同urllib2的urlopen方法,也可以传入request
response = opener. open ( 'http://brioncd.asml.com' )
for item in cookie:
   print 'Name = ' + item.name
   print 'Value = ' + item.value

1.7.2 cookie保存到文件

在上面的方法中,我们将cookie保存到了cookie这个变量中,如果我们想将cookie保存到文件中该怎么做呢?这时,我们就要用到FileCookieJar这个对象了,在这里我们使用它的子类MozillaCookieJar来实现Cookie的保存。

# -- cookie save to file
httpHandler = urllib2.HTTPHandler(debuglevel = 1 )
httpsHandler = urllib2.HTTPSHandler(debuglevel = 1 )
#设置保存cookie的文件,同级目录下的cookie.txt
filename = 'cookie.txt'
#声明一个MozillaCookieJar对象实例来保存cookie,之后写入文件
cookie = cookielib.MozillaCookieJar(filename)
#利用urllib2库的HTTPCookieProcessor对象来创建cookie处理器
handler = urllib2.HTTPCookieProcessor(cookie)
#通过handler来构建opener
opener = urllib2.build_opener(httpHandler, httpsHandler, handler)
#创建一个请求,原理同urllib2的urlopen
response = opener. open ( "http://brioncd.asml.com" )
#保存cookie到文件
cookie.save(ignore_discard = True , ignore_expires = True )

ignore_discard的意思是即使cookies将被丢弃也将它保存下来,ignore_expires的意思是如果在该文件中cookies已经存在,则覆盖原文件写入,在这里,我们将这两个全部设置为True。运行之后,cookies将被保存到cookie.txt文件中,我们查看一下内容:

# Netscape HTTP Cookie File
# This is a generated file!  Do not edit.
brioncd.asml.com   FALSE  /  FALSE  1516787043 sessionid  01b4b97f258db68f21cb987f39ee2947

1.7.3 从文件中获取cookie并访问

我们已经做到把Cookie保存到文件中了,如果以后想使用,可以利用下面的方法来读取cookie并访问网站。

# -- load cookie from file
httpHandler = urllib2.HTTPHandler(debuglevel = 1 )
httpsHandler = urllib2.HTTPSHandler(debuglevel = 1 )
#创建MozillaCookieJar实例对象
cookie = cookielib.MozillaCookieJar()
#从文件中读取cookie内容到变量
cookie.load( 'cookie.txt' , ignore_discard = True , ignore_expires = True )
#利用urllib2库的HTTPCookieProcessor对象来创建cookie处理器
handler = urllib2.HTTPCookieProcessor(cookie)
#利用urllib2的build_opener方法创建一个opener
opener = urllib2.build_opener(httpHandler, httpsHandler, handler)
#创建请求的request
req = urllib2.Request( "http://brioncd.asml.com" )
response = opener. open (req)

设想,如果我们的 cookie.txt 文件中保存的是某个人登录网站的cookie,那么我们提取出这个cookie文件内容,就可以用以上方法模拟这个人的账号登录网站。

1.7.4 利用cookie模拟登入网站

下面我们以我们的brioncd为例,利用cookie实现模拟登录,并将cookie信息保存到文本文件中,来感受一下cookie大法吧!

# -- login by cookie
httpHandler = urllib2.HTTPHandler(debuglevel = 1 )
httpsHandler = urllib2.HTTPSHandler(debuglevel = 1 )
filename = 'cookie.txt'
#声明一个MozillaCookieJar对象实例来保存cookie,之后写入文件
cookie = cookielib.MozillaCookieJar(filename)
#利用urllib2库的HTTPCookieProcessor对象来创建cookie处理器
handler = urllib2.HTTPCookieProcessor(cookie)
opener = urllib2.build_opener(httpHandler, httpsHandler, handler)
postdata = urllib.urlencode({ 'username' : 'tanli' ,
    'password' : '123456' ,
    'blogin' : 'Login' ,
    'validate_email' : 'yes' })
#登录URL
#模拟登录,并把cookie保存到变量
result = opener. open (loginUrl, postdata)
#保存cookie到cookie.txt中
cookie.save(ignore_discard = True , ignore_expires = True )
#利用cookie请求访问另一个网址
#请求访问网址
result = opener. open (mainUrl)
print result.read()

1.8 一个完整的爬虫例子

前面的基础知识,我们已经可以获取网页的内容,不过还差一步,从这杂乱的html代码中提取出我们需要的内容,利用python的正则表达式可以完成提取。下面是一个例子,我们从brioncd提取前3页的questions

# -- crawl brioncd questions by urlopen and regular expression
try :
    page_num = 1
    total_num = 3
    while page_num < = total_num:
        url = base_url + str (page_num)
        request = urllib2.Request(url)
        response = urllib2.urlopen(request)
        content = response.read().decode( 'utf-8' )
        pattern = re. compile ( '<div.*?short-summary">'
            + '.*?<div.*?item-count">(.*?)</div>'
            + '.*?<div.*?item-count">(.*?)</div>'
            + '.*?<div.*?item-count">(.*?)</div>'
            + '.*?<div.*?question-summary-wrapper">.*?<a.*?>(.*?)</a>'
            + '.*?<div.*?userinfo">.*?<span.*?>(.*?)</span>.*?<a.*?>(.*?)</a>'
            , re.S)
        items = re.findall(pattern, content)
        print "page " , page_num, url
        for item in items:
            print item[ 0 ], item[ 1 ], item[ 2 ], item[ 3 ], item[ 4 ], item[ 5 ]
        page_num + = 1
except urllib2.HTTPError, e:
   print e.code, e.reason
except urllib2.URLError, e:
   print e.reason

2 爬虫工具

之前我们用了urllib 库,这个作为入门的工具还是不错的,对了解爬虫的基本理念,掌握爬虫爬取的流程有所帮助。入门之后,我们就需要学习一些更加高级的内容和工具来方便我们的爬取。

2.1 Request库用法

先介绍一下 requests 库的基本用法。

# -- request lib get web page
import requests
r = requests.get( 'http://brioncd.asml.com' )
print type (r)
print r.status_code
print r.headers
print r.encoding
#print r.text
print r.cookies

Get方法,设置header, 传送data

# -- request lib get command
import requests
headers = { 'User-Agent' : 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36' }
data = { 'sort' : 'mostvoted' ,
      'pagesize' : '15' }
r = requests.get(url, params = data, headers = headers)
print r.url
print r.status_code
#print r.text

Post方法

# -- request lib post command
import requests
values = { 'username' : 'tanli' ,
    'password' : '123456' ,
    'blogin' : 'Login' ,
    'validate_email' : 'yes' }
r = requests.post(url, values)
print r.status_code
print r.url
print r.text

利用Request库来实现爬取brioncd的questions

# -- crawl brioncd questions by request library
import requests
try :
    page_num = 1
    total_num = 3
    while page_num < = total_num:
        url = base_url + str (page_num)
        response = requests.get(url)
        content = response.text
        pattern = re. compile ( '<div.*?short-summary">'
            + '.*?<div.*?item-count">(.*?)</div>'
            + '.*?<div.*?item-count">(.*?)</div>'
            + '.*?<div.*?item-count">(.*?)</div>'
            + '.*?<div.*?question-summary-wrapper">.*?<a.*?>(.*?)</a>'
            + '.*?<div.*?userinfo">.*?<span.*?>(.*?)</span>.*?<a.*?>(.*?)</a>'
            , re.S)
        items = re.findall(pattern, content)
        print "page " , page_num, url
        for item in items:
            print item[ 0 ], item[ 1 ], item[ 2 ], item[ 3 ], item[ 4 ], item[ 5 ]
        page_num + = 1
except urllib2.HTTPError, e:
   print e.code, e.reason
except urllib2.URLError, e:
   print e.reason

更多Request用法 http://docs.python-requests.org/en/master/

2.2 Beautiful Soup用法

上面我们介绍了正则表达式,它的内容其实还是蛮多的,如果一个正则匹配稍有差池,那可能程序就处在永久的循环之中,而且有的小伙伴们也对写正则表达式的写法用得不熟练,没关系,我们还有一个更强大的工具,叫Beautiful Soup,有了它我们可以很方便地提取出HTML或XML标签中的内容,实在是方便,这一节就让我们一起来感受一下Beautiful Soup的强大吧。

Beautiful Soup是python的一个库,最主要的功能是从网页抓取数据。官方解释如下:

Beautiful Soup提供一些简单的、python式的函数用来处理导航、搜索、修改分析树等功能。它是一个工具箱,通过解析文档为用户提供需要抓取的数据,因为简单,所以不需要多少代码就可以写出一个完整的应用程序。

Beautiful Soup自动将输入文档转换为Unicode编码,输出文档转换为utf-8编码。你不需要考虑编码方式,除非文档没有指定一个编码方式,这时,Beautiful Soup就不能自动识别编码方式了。然后,你仅仅需要说明一下原始编码方式就可以了。

Beautiful Soup已成为和lxml、html6lib一样出色的python解释器,为用户灵活地提供不同的解析策略或强劲的速度。

Beautiful Soup支持Python标准库中的HTML解析器,还支持一些第三方的解析器,如果我们不安装它,则 Python 会使用 Python默认的解析器,lxml 解析器更加强大,速度更快,推荐安装。

解析器

使用方法

优势

劣势

Python标准库 BeautifulSoup(markup, “html.parser”)
  • Python的内置标准库
  • 执行速度适中
  • 文档容错能力强
  • Python 2.7.3 or 3.2.2)前 的版本中文档容错能力差
lxml HTML 解析器 BeautifulSoup(markup, “lxml”)
  • 速度快
  • 文档容错能力强
  • 需要安装C语言库
lxml XML 解析器 BeautifulSoup(markup, [“lxml”, “xml”])BeautifulSoup(markup, “xml”)
  • 速度快
  • 唯一支持XML的解析器
  • 需要安装C语言库
html5lib BeautifulSoup(markup, “html5lib”)
  • 最好的容错性
  • 以浏览器的方式解析文档
  • 生成HTML5格式的文档
  • 速度慢
  • 不依赖外部扩展
# -- beautifulsoup basic usage
from bs4 import BeautifulSoup
html = """
<html>
<head><title>The Dormouse's story</title></head>
<body>
<p class="title" name="dromouse"><b>The Dormouse's story</b></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"><!-- Elsie -->ss</a>,
<a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and
<a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>;
and they lived at the bottom of a well.</p>
<p class="story">...</p>
"""
soup = BeautifulSoup(html, "lxml" )
#soup = BeautifulSoup(open('file.html'))
print soup.prettify()
# soup.tagname to get tag, only return the first matched tag
print soup.title
print soup.head
print soup.a
print soup.p
# <class 'bs4.BeautifulSoup'>
print type (soup)
# <class 'bs4.element.Tag'>
print type (soup.a)
# get tag's name and attrs
print soup.p.name
print soup.p.attrs
print soup.p[ 'class' ]
print soup.p.get( 'class' )
# get tag's text
print soup.p.string
# <class 'bs4.element.NavigableString'>
print type (soup.p.string)
# find all tags by tag name
print soup.find_all( 'b' )
print soup.find_all( 'a' )
# find all tag like 'b...'
import re
for tag in soup.find_all(re. compile ( "^b" )):
    print tag.name
# find all tag 'a' or 'b'
print soup.find_all([ 'a' , 'b' ])
# find all tags has class but no id
def has_class_but_no_id(tag):
    return tag.has_attr( 'class' ) and not tag.has_attr( 'id' )
for tag in soup.find_all(has_class_but_no_id):
    print tag.name
# find all tag with "id = link2"
print soup.find_all( id = 'link2' )
# find all tags with href re match
print soup.find_all(href = re. compile ( "elsie" ))
# find all tags with multi attrs match
print soup.find_all(href = re. compile ( "elsie" ), id = 'link1' )
# find all 'a' tags with class attr
print soup.find_all( "a" , class_ = "sister" )
# css select tags
print soup.select( 'title' )
# css select tags with class = 'sister'
print soup.select( '.sister' )
# css select tags with id = 'link1'
print soup.select( '#link1' )
# css select tags with id = 'link1' in p tag
print soup.select( 'p #link1' )
# css select 'a' tags with class = 'sister'
print soup.select( 'a[class="sister"]' )
# css select all tags with href="http://example.com/elsie" in p tag
print soup.select( 'p a[href="http://example.com/elsie"]' )

详细用法请参考 官方文档

同样我们用Beautiful soap 来实现上面的brioncd questions的爬取

# -- crawl brioncd questions by beautifulsoup
from bs4 import BeautifulSoup
try :
    page_num = 1
    total_num = 3
    while page_num < = total_num:
        url = "%s%d" % (base_url, page_num)
        print "page " , page_num, url
        request = urllib2.Request(url)
        response = urllib2.urlopen(request)
        content = response.read().decode( 'utf-8' )
        soup = BeautifulSoup(content, 'lxml' )
        tags = soup.find_all( "div" , class_ = "short-summary" );
        for tag in tags:
            str = ""
            item_count_divs = tag.find_all( "div" , class_ = "item-count" )
            countStr = "%s %s %s" % (item_count_divs[ 0 ].string, item_count_divs[ 1 ].string, item_count_divs[ 2 ].string)
            title = tag.find( "h2" ).string
            time = tag.find( "span" , class_ = 'relativetime' ).string
            user = tag.select( 'div[class="userinfo"] a' )[ 0 ].string
            print countStr, title, time, user
        page_num = page_num + 1
except urllib2.HTTPError, e:
    print e.code, e.reason
except urllib2.URLError, e:
    print e.reason

2.3 lxml库与Xpath语法的用法

前面我们介绍了 BeautifulSoup 的用法,这个已经是非常强大的库了,不过还有一些比较流行的解析库,例如 lxml,使用的是 Xpath 语法,同样是效率比较高的解析方法。如果大家对 BeautifulSoup 使用不太习惯的话,可以尝试下 Xpath。下面直接从code入手了解其使用方法。

# -- lxml and Xpath basic usage
from lxml import etree
text = '''
<div>
   <ul>
      <li class="item-0"><a href="link1.html">first item</a></li>
      <li class="item-1"><a href="link2.html">second item</a></li>
      <li class="item-inactive"><a href="link3.html"><span class="bold">third item</span>/a></li>
      <li class="item-1"><a href="link4.html">fourth item</a></li>
      <li class="item-0"><a href="link5.html">fifth item</a>
    </ul>
  </div>
'''
html = etree.HTML(text)
#html = etree.parse('file.html')
print etree.tostring(html)
print type (html)
# get all 'li' tags
result = html.xpath( '//li' )
print result
print len (result)
# <type 'list'>
print type (result)
# <type 'lxml.etree._Element'>
print type (result[ 0 ])
# get all 'li' tag's class
print html.xpath( '//li/@class' )
# get all 'a' tags with  attr(href = link1.html) in 'li' tag
print html.xpath( '//li/a[@href="link1.html"]' )
# get all 'span' tags in 'li' tag
print html.xpath( '//li//span' )
# get all 'a' tag's attr class in 'li' tag
print html.xpath( '//li/a//@class' )
# get 'a' tag's attr href in last 'li' tag
print html.xpath( '//li[last()]/a/@href' )
# get 'a' tag's text in last -1 'li' tag
print html.xpath( '//li[last()-1]/a' )[ 0 ].text

更多用法请参考如下链接

下面利用lxml实现brioncd questions的爬取

# -- crawl brioncd questions by beautifulsoup by lxml and xpath
from lxml import etree
try :
    page_num = 1
    total_num = 3
    while page_num < = total_num:
        url = "%s%d" % (base_url, page_num)
        print "page " , page_num, url
        request = urllib2.Request(url)
        response = urllib2.urlopen(request)
        content = response.read().decode( 'utf-8' )
        html = etree.HTML(content)
        divtags = html.xpath( '//div[@class="short-summary"]' )
        for divtag in divtags:
            countList = divtag.xpath( './/div[@class="item-count"]/text()' )
            title = divtag.xpath( './/h2/a[@title]/text()' )[ 0 ]
            time = divtag.xpath( './/div[@class="userinfo"]/span[@class="relativetime"]/text()' )[ 0 ]
            user = divtag.xpath( './/div[@class="userinfo"]/a/text()' )[ 0 ]
            print countList[ 0 ], countList[ 1 ], countList[ 2 ], title, time, user
        page_num + = 1
except urllib2.HTTPError, e:
    print e.code, e.reason
except urllib2.URLError, e:
    print e.reason

2.4 PyQuery的用法

你是否觉得 XPath 的用法多少有点晦涩难记呢?你是否觉得 BeautifulSoup 的语法多少有些悭吝难懂呢?你是否甚至还在苦苦研究正则表达式却因为少些了一个点而抓狂呢?你是否已经有了一些前端基础了解选择器却与另外一些奇怪的选择器语法混淆了呢?

PyQuery 来了,乍听名字,你一定联想到了 jQuery,如果你对 jQuery 熟悉,那么 PyQuery 来解析文档就是不二之选。Pyquery 可让你用 jQuery 的语法来对 xml 进行操作。这I和 jQuery 十分类似。如果利用 lxml,pyquery 对 xml 和 html 的处理将更快。注意这个库不是一个可以和 JavaScript交互的代码库,它只是非常像 jQuery API 而已。

# -- pyquery basic usage
from pyquery import PyQuery as pq
html = '''
<div>
    <p id="hello" class="hello">hello world</p>
    <ul>
        <li class="item-0">first item</li>
        <li class="item-1"><a href="link2.html">second item</a></li>
        <li class="item-0 active"><a href="link3.html"><span class="bold">third item</span></a></li>
        <li class="item-1 active"><a href="link4.html">fourth item</a></li>
        <li class="item-0"><a href="link5.html">fifth item</a></li>
    </ul>
</div>
'''
doc = pq(html)
# doc = pq(filename='file.html')
print doc.html()
print type (doc)
li = doc( 'li' )
print type (li)
print li.text()
p = doc( 'p' )
print p.attr( "id" )
print p.addClass( 'beauty' )
print p.css( 'font-size' , '16px' )
print p.css({ 'background-color' : 'yellow' })
print "hi"
print doc( 'div' )( 'p' )
print doc( 'p' ). filter ( '.hello' )
print doc( 'div' ).find( 'li' )

更多用法请参考 pyquery官方文档

同样用pyquery来实现爬取brioncd的question列表

# -- crawl brioncd questions by pyquery
from pyquery import PyQuery as pq
from lxml import etree
try :
    page_num = 1
    total_num = 3
    while page_num < = total_num:
        url = "%s%d" % (base_url, page_num)
        print "page " , page_num, url
        doc = pq( 'http://brioncd.asml.com' )
        divs = doc( 'div' ). filter ( '.short-summary' )
        for div in divs.items():
            count = div( '.item-count' ).text()
            title = div( 'h2' )( 'a' ).text()
            time = div( '.relativetime' ).text()
            user = div( '.userinfo' )( 'a' ).text()
            print count, title, time, user
        page_num + = 1
except urllib2.HTTPError, e:
    print e.code, e.reason
except urllib2.URLError, e:
    print e.reason

3 爬虫框架

爬虫入门之后,我们有两种方法学习爬虫进阶知识

  • 学习设计模式的一些知识,强化Python相关知识,自己动手造轮子,继续为自己的爬虫增加分布式,多线程等功能扩展。
  • 使用一些优秀爬虫框架,这些现成的框架简单易用,可以快速实现一些爬虫要求,当然了如果要进一步定制,或者是个性化需求, 就需要深入学习它的源码等知识,进一步强化

3.1 Pyspider框架

PySpiderbinux 做的一个爬虫架构的开源化实现。主要的功能需求是:

  • 抓取、更新调度多站点的特定的页面
  • 需要对页面进行结构化信息提取
  • 灵活可扩展,稳定可监控

而这也是绝大多数python爬虫的需求 —— 定向抓取,结构化化解析。但是面对结构迥异的各种网站,单一的抓取模式并不一定能满足,灵活的抓取控制是必须的。为了达到这个目的,单纯的配置文件往往不够灵活,于是,通过脚本去控制抓取是最后的选择。

而去重调度,队列,抓取,异常处理,监控等功能作为框架,提供给抓取脚本,并保证灵活性。最后加上web的编辑调试环境,以及web任务监控,即成为了这套框架。

pyspider的设计基础是: 以python脚本驱动的抓取环模型爬虫

  • 通过python脚本进行结构化信息的提取,follow链接调度抓取控制,实现最大的灵活性
  • 通过web化的脚本编写、调试环境。web展现调度状态
  • 抓取环模型成熟稳定,模块间相互独立,通过消息队列连接,从单进程到多机分布式灵活拓展

pyspider的架构主要分为 scheduler(调度器), fetcher(抓取器), processor(脚本执行):

  • 各个组件间使用消息队列连接,除了scheduler是单点的,fetcher 和 processor 都是可以多实例分布式部署的。 scheduler 负责整体的调度控制
  • 任务由 scheduler 发起调度,fetcher 抓取网页内容, processor 执行预先编写的python脚本,输出结果或产生新的提链任务(发往 scheduler),形成闭环。
  • 每个脚本可以灵活使用各种python库对页面进行解析,使用框架API控制下一步抓取动作,通过设置回调控制解析动作。

PySpider安装

  1. 安装pip
  2. 安装phantomjs
  3. pip install pyspider

pyspider all 启动, 然后浏览器访问 http:;//localhost:5000, 观察一下效果,如果可以正常出现 PySpider 的页面,那证明一切OK

3.2 Scrapy框架

pyspider上手更简单,操作更加简便,因为它增加了 WEB 界面,写爬虫迅速,集成了phantomjs,可以用来抓取js渲染的页面。Scrapy自定义程度高,比 PySpider更底层一些,适合学习研究,需要学习的相关知识多,不过自己拿来研究分布式和多线程等等是非常合适的。这里不详细介绍了,更多细节请参考。

https://scrapy.org/


分享给小伙伴们:
本文标签: PythonSpider

相关文章

发表评论愿您的每句评论,都能给大家的生活添色彩,带来共鸣,带来思索,带来快乐。

CopyRight © 2015-2016 QingPingShan.com , All Rights Reserved.

清屏网 版权所有 豫ICP备15026204号