介绍和原生库

大部分爬虫教材会把网络爬虫比作一只蜘蛛,而蜘蛛网则是我们的网络,这只蜘蛛会把触手伸向不同的地方,从而获取网络上的内容。

参考资料

事实上,我认为一个好的爬虫,或者是蜘蛛,应当具备以下素质:

  • 网络爬虫(又被称为网页蜘蛛,网络机器人,在FOAF社区中间,更经常的称为网页追逐者),是一种按照一定的规则,自动地抓取万维网信息的程序或者脚本。另外一些不常使用的名字还有蚂蚁、自动索引、模拟程序或者蠕虫。
  • 其实通俗的讲就是通过程序去获取web页面上自己想要的数据,也就是自动抓取数据
  • 爬虫的本质:模拟浏览器打开网页,获取网页中我们想要的那部分数据

  • 浏览器打开网页的过程:

    • 当你在浏览器中输入地址后,经过DNS服务器找到服务器主机,向服务器发送一个请求,服务器经过解析后发送给用户浏览器结果,包括html,js,css等文件内容,浏览器解析出来最后呈现给用户在浏览器上看到的结果
    • 用户看到的浏览器的结果就是由HTML代码构成的,我们爬虫就是为了获取这些内容,通过分析和过滤html代码,从中获取我们想要资源(文本,图片,视频…..)
  • 爬虫就是请求网站并提取数据的自动化程序。其中请求提取自动化是爬虫的关键!
  • 不勤劳的蜘蛛不是一只好爬虫

爬虫的基本流程

  • 发起请求
    • 通过HTTP库向目标站点发起请求,也就是发送一个Request,请求可以包含额外的header等信息,等待服务器响应Response
  • 获取响应内容
    • 如果服务器能正常响应,会得到一个Response,Response的内容便是所要获取的页面内容,类型可能是HTML,Json字符串,二进制数据(图片或者视频)等类型
  • 解析内容
    • 得到的内容可能是HTML,可以用正则表达式,页面解析库进行解析,可能是Json,可以直接转换为Json对象解析,可能是二进制数据,可以做保存或者进一步的处理
  • 保存数据
    • 保存形式多样,可以存为文本,也可以保存到数据库,或者保存特定格式的文件

这是作为一只存活在互联网这张大网上的蜘蛛的基本素养。所谓勤劳,就是能够完成自己的基本使命,
孜孜不倦,任劳任怨

什么是Request,Response

  • 浏览器发送消息给网址所在的服务器,这个过程就叫做HTPP Request
  • 服务器收到浏览器发送的消息后,能够根据浏览器发送消息的内容,做相应的处理,然后把消息回传给浏览器,这个过程就是HTTP Response
  • 浏览器收到服务器的Response信息后,会对信息进行相应的处理,然后展示
  • 不机灵的蜘蛛死的早

Request中包含什么?

试想一下,蜘蛛在自己的八卦阵中迷失了方向,那可能只剩下死掉了,所以作为蜘蛛,除了要勤劳外,还需要机灵,具体描述就是:不迷失方向
遇到困难不退缩不干不必要的工作

请求方式

主要有:GET/POST两种类型常用,另外还有HEAD/PUT/DELETE/OPTIONS

  • GETPOST的区别就是:
    • GET是把参数数据队列加到提交表单的ACTION属性所指的URL中,值和表单内各个字段一一对应,在URL中可以看到。
    • POST是通过HTTP POST机制,将表单内各个字段放置在HTML
      HEADER内一起传送到ACTION属性所指的URL中(用户看不到此过程)
    • GET:从服务器上获取数据。使用GET方法应该只用在读取数据,而不应当被用于产生“副作用”的操作中,例如在Web
      Application中。其中一个原因是GET可能会被网络蜘蛛等随意访问。
    • POST:向指定资源提交数据,请求服务器进行处理(例如提交表单或者上传文件)。
  • HEAD:与GET方法一样,都是向服务器发出指定资源的请求。只不过服务器将不传回资源的本文部分。它的好处在于,使用这个方法可以在不必传输全部内容的情况下,就可以获取其中“关于该资源的信息”(元信息或称元数据)。
  • PUT:向指定资源位置上传其最新内容。
  • OPTIONS:这个方法可使服务器传回该资源所支持的所有HTTP请求方法。用’*‘来代替资源名称,向Web服务器发送OPTIONS请求,可以测试服务器功能是否正常运作。
  • DELETE:请求服务器删除Request-URI所标识的资源。
  • 不严谨的蜘蛛跑断腿也没用

请求URL

URL,即统一资源定位符,也就是我们说的网址,统一资源定位符是对可以从互联网上得到的资源的位置和访问方法的一种简洁的表示,是互联网上标准资源的地址。互联网上的每个文件都有一个唯一的URL,它包含的信息指出文件的位置以及浏览器应该怎么处理它。

URL的格式由三个部分组成:

  • 协议(或称为服务方式)。
  • 存有该资源的主机IP地址(有时也包括端口号)。
  • 主机资源的具体地址,如目录和文件名等。

爬虫爬取数据时必须要有一个目标的URL才可以获取数据,因此,它是爬虫获取数据的基本依据。

尽管我们的蜘蛛已经聪明又勤劳了,但是如果粗心大意不严谨,大部分情况下爬了好久好久还是一点食物都吃不到,原因就是不严谨,可能找到了过期的、不能吃的食物

请求头

包含请求时的头部信息,如User-Agent,Host,Cookies等信息

我们了解了一只网络蜘蛛的基本素养,接下来我们就开始学习它吧:

请求体

请求携带的数据,如提交表单数据时候的表单数据(POST)

参见上面网络蜘蛛的基本素养,我们大概已经知道了爬虫需要做的事情,无非就是如下几个阶段:

Response中包含了什么

所有HTTP响应的第一行都是状态行,依次是当前HTTP版本号,3位数字组成的状态代码,以及描述状态的短语,彼此由空格分隔。

  1. 确定目标,也就是得到需要获取内容的url
  2. 伪装接近,也就是要骗过目标的防御系统,如伪装成浏览器
  3. 发送请求,获取信任之后,我们就可以大胆的发送请求到目的地了
  4. 捕获响应,当目标有了反馈以后捕获响应的内容
  5. 获取数据,拿到响应的数据之后,需要对数据进行分析和筛选
  6. 重复1-5 , 确定新目标,然后继续做一名勤劳、机灵严谨的蜘蛛

响应状态

有多种响应状态,如:
200代表成功,301跳转,404找不到页面,502服务器错误 1xx消息——请求已被服务器接收,继续处理 2xx成功——请求已成功被服务器接收、理解、并接受 3xx重定向——需要后续操作才能完成这一请求 4xx请求错误——请求含有词法错误或者无法被执行 5xx服务器错误——服务器在处理某个正确请求时发生错误 常见代码: 200 OK 请求成功 400 Bad Request 客户端请求有语法错误,不能被服务器所理解 401 Unauthorized 请求未经授权,这个状态代码必须和WWW-Authenticate报头域一起使用 403 Forbidden 服务器收到请求,但是拒绝提供服务 404 Not Found 请求资源不存在,eg:输入了错误的URL 500 Internal Server Error 服务器发生不可预期的错误 503 Server Unavailable 服务器当前不能处理客户端的请求,一段时间后可能恢复正常 301 目标永久性转移 302 目标暂时性转移

用python来讲,就是通过代码实现对目标地址的访问(走完http
request和response的流程,获取response之后再做数据筛选)

响应头

如内容类型,类型的长度,服务器信息,设置Cookie

python中的urllib和urllib2两个原生库提供了我们发送http request和获取 http
response的全部实现。通过使用这两个库,可以很简单的获取网络内容,大致分为如下几步。

响应体

最主要的部分,包含请求资源的内容,如网页HTMl,图片,二进制数据等

确定目标

也就是确定我们要爬区的页面的url

能爬取什么样的数据

  • 网页文本:如HTML文档,Json格式化文本等
  • 图片:获取到的是二进制文件,保存为图片格式
  • 视频:同样是二进制文件
  • 其他:只要请求到的,都可以获取

伪装接近

很多站点都对用户访问做了一些验证,比如通过查看headers内的信息,来确定访问来源

HTTP
Headers是HTTP请求和相应的核心,它承载了关于用户端流览器,请求页面,伺服器等相关的资讯。具体关于headers的详细信息参见
什么是HTTP Headers

比如,大部分网站会验证Headers内的 User-Agent
来确定请求是不是从浏览器发出,我们则可以伪造一个user-agent封装成headers(headers在python内以字典的结构表现)

如何解析数据

  • 直接处理
  • Json解析
  • 正则表达式处理
  • BeautifulSoup解析处理
  • PyQuery解析处理
  • XPath解析处理

    import urllib

发送请求

发送请求之前,我们需要和headers一样,创建一个请求request

如果是带参数的请求,我们还需要创造参数data

urllib2内可以直接调用 urllib2.Request(url, data,
headers)方法,这个方法返回一个request

Urllib库的简介

Urllib是python内置的HTTP请求库,包括以下模块

  • urllib.request: 请求模块
  • urllib.error: 异常处理模块
  • urllib.parse: url解析模块
  • urllib.response: 响应请求

    • Type: module
    • Docstring:
      Response classes used by urllib.

    • The base class, addbase, defines a minimal file-like
      interface,including read() and readline().

    • The typical response object is an addinfourl instance, which
      defines an info() method that returns
      headers and a geturl() method that returns the url.

捕获响应

当我们准备好了request(带数据或者不带数据)之后,即可发送请求到页面,然后会得到一个response

urllib2内可以使用 urllib2.urlopen(request,
timeout)方法,该方法返回一个response;
要获取response的内容,则需要使用 response.read()方法

代码示例(爬取 mm131首页上的内容):

# -*- encoding:utf-8 -*-import urllib2 #这是我们需要使用的工具url = "http://www.mm131.com" #这是我们的目的地址#创建一个headers 内含user-agent伪装成一个mozilla4.0浏览器headers = {'User-Agent': 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)'}#创建一个request, 传入url data(没有参数的访问则为None) 和 headersrequest = urllib2.Request(url,data=None, headers=headers)#获取响应response,发送request,设置超时时间为5sresponse = urllib2.urlopen(request, timeout = 5)#读取response的内容,同时将gb2312转换为utf-8,ignore为忽略非法字符content = response.read().decode('gb2312', 'ignore')print content

通过
urllib2可以轻松的获取目标页面内容,那么最让人头疼的事情来了,那就是数据分析

这里我们暂时只使用最笨的一种方法,那就是在本地进行字符串的匹配来获取想要的内容,这里则需要使用到正则表达式,关于正则表达式可以参考《python核心编程
第三版》的第一章,也可以观看imooc的视频python正则表达式,利用两个小时的时间对正则表达式有一个全面的了解。

图片 1通过分析源文件找到适合的匹配规则

比如,上面得到的内容,我们可以对美女图片的代码进行分析,并作出匹配:

示例代码(对上面获得的content进行正则匹配,得到其中的图片):

import repattern = re.compile('<li .*?src="".*?alt="图片 2"', re.S)items = re.findall(pattern,content)

上面代码中的 compile 表示匹配规则,
其中.?可以理解为忽略这期间的内容,我们从<li
开始匹配,忽略中间的内容直到 src=”出现,然后获取中间的内容
带上括号表示这些内容在后面fiandall中会被保存,以此类推,我们第二个代表的则是alt内的内容,也就是图片的描述。

re.findall(compile,
content)反回一个集合类型,包含页面中所有符合匹配规则的元素的集合,也就是所有美女图片的地址和描述。

可以使用迭代器,访问他们:

for item in items: print item[0] #item[0]也就是我们匹配到的第一个元素 src print item[1] #item[0]也就是我们匹配到的第二个元素 alt

数据持久化的方式是多样的我们可以使用数据库来存储,对于大型数据分析,我们还可以使用缓存工具
memchche 或者
redis将数据快速保存,之后持久化到数据库中,甚至还有数据集群(再扯久没边了,参考google
机器人深度学习)

其次我们还可以保存内容到本地文件,比较流行的是直接写入到某一个服务器的web目录,这样数据爬取下来后,还可以通过web直接访问。

这里,我们主要是保存图片,因此直接将数据保存到本地,而且保存成图片格式。urllib中(注意时urllib而非urllib2)提供了非常方便的方法。具体参见代码:

for item in items: urllib.urlretrieve(item[0],item[1]+'.jpg')

urllib.urlretrieve(url, local, Schedule)
函数可以从url下载内容保存到本地路径local中,其中还可以加入第三个参数
(回调函数,主要实现类似于进度条的功能),具体函数解析请参考
现代魔法学院 urllib解析

# -*- encoding:utf-8 -*-import urllib2 #这是我们需要使用的工具import urllib #注意urllib 和 urllib2并不同import reimport sysreloadsys.setdefaultencoding #在这里统一文件的编码为utf-8url = "http://www.mm131.com" #这是我们的目的地址try: #创建一个headers 内含user-agent伪装成一个mozilla4.0浏览器 headers = {'User-Agent': 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)'} #创建一个request, 传入url data(没有参数的访问则为None) 和 headers request = urllib2.Request(url,data=None, headers=headers) #获取响应response,发送request,设置超时时间为5s response = urllib2.urlopen #读取response的内容 content = response.read().decode('gb2312','ignore') pattern = re.compile('<li .*?src="".*?alt="图片 3"', re.S) items = re.findall(pattern,content) for item in items: print '开始下载图片: %s' % item[1] urllib.urlretrieve(item[0],item[1]+'.jpg')except Exception, e: print '网络错误' raise e

urllib.request

urllib.request.urlopen

urllib.request.urlopen(url, data=None, [timeout, ]*, cafile=None, capath=None, cadefault=False, context=None)
参数:
Open the URL url, which can be either a string or a Request
object.(url可以是一个网址的字符串,或是一个Request对象)
data must be an object specifying additional data to be sent to the
server, or None if no such data is needed. See Request for details.

 ```
 返回一个file-like对象(可以像操作context一样进行管理和拥有一些实例方法):
 ```
    For HTTP and HTTPS URLs, this function returns a http.client.HTTPResponse
    object slightly modified. In addition to the three new methods above, the
    msg attribute contains the same information as the reason attribute ---
    the reason phrase returned by the server --- instead of the response
    headers as it is specified in the documentation for HTTPResponse.

    For FTP, file, and data URLs and requests explicitly handled by legacy
    URLopener and FancyURLopener classes, this function returns a
    urllib.response.addinfourl object.

    Note that None may be returned if no handler handles the request (though
    the default installed global OpenerDirector uses UnknownHandler to ensure
    this never happens).

    In addition, if proxy settings are detected (for example, when a *_proxy
    environment variable like http_proxy is set), ProxyHandler is default
    installed and makes sure the requests are handled through the proxy.

常用实例方法

  • geturl() – return the URL of the resource retrieved, commonly used
    to
    determine if a redirect was followed

  • info() – return the meta-information of the page, such as headers,
    in the
    form of an email.message_from_string() instance (see Quick
    Reference to
    HTTP Headers)

  • getcode() – return the HTTP status code of the response. Raises
    URLError
    on errors.

示例:

import urllib.request
response = urllib.request.urlopen('https://www.baidu.com')   # url是网址字符串
page = response.read()                # response.read()可以获取到网页的内容,获取响应资源
decode_page = page.decode('utf-8')    # 统一编码格式
print(decode_page)

<html>
<head>
    <script>
        location.replace(location.href.replace("https://","http://"));
    </script>
</head>
<body>
    <noscript><meta http-equiv="refresh" content="0;url=http://www.baidu.com/"></noscript>
</body>
</html>

import urllib
req = urllib.request.Request('https://www.baidu.com')
response = urllib.request.urlopen(req)   # url是一个Request对象
the_page = response.read().decode('utf-8')
print(the_page)

<html>
<head>
    <script>
        location.replace(location.href.replace("https://","http://"));
    </script>
</head>
<body>
    <noscript><meta http-equiv="refresh" content="0;url=http://www.baidu.com/"></noscript>
</body>
</html>
对象名 类型
req urllib.request.Request
response http.client.HTTPResponse
response.read() bytes
response.info() http.client.HTTPMessage
the_page str
type(response.info())

http.client.HTTPMessage

print(response.geturl())     # 返回真实网址
print(response.getcode())    # 返回`HTTP`响应代码
print(response.info())   # 返回从服务器传回的`MIME`标签头

https://www.baidu.com
200
Accept-Ranges: bytes
Cache-Control: no-cache
Content-Length: 227
Content-Type: text/html
Date: Wed, 23 Aug 2017 11:44:38 GMT
Last-Modified: Mon, 21 Aug 2017 07:02:00 GMT
P3p: CP=" OTI DSP COR IVA OUR IND COM "
Pragma: no-cache
Server: BWS/1.1
Set-Cookie: BD_NOT_HTTPS=1; path=/; Max-Age=300
Set-Cookie: BIDUPSID=2BA9A96F1CBA72FD5B271C3376C4397B; expires=Thu, 31-Dec-37 23:55:55 GMT; max-age=2147483647; path=/; domain=.baidu.com
Set-Cookie: PSTM=1503488678; expires=Thu, 31-Dec-37 23:55:55 GMT; max-age=2147483647; path=/; domain=.baidu.com
Strict-Transport-Security: max-age=0
X-Ua-Compatible: IE=Edge,chrome=1
Connection: close

urlopen一般常用的有三个参数,它的参数如下:urllib.requeset.urlopen(url,data,timeout)

  • data参数的使用
    上述的例子是通过请求的get请求获得,下面使用urllib的post请求
    这里通过http://httpbin.org/post网站演示(该网站可以作为练习使用urllib的一个站点使用,可以
    模拟各种请求操作)。

    # urlopen传入Request对象
    import urllib
    url = ”
    values = {‘name’:’WHY’,

         'localtion':'SDU',
         'language':'Python'}
    

    data = bytes(urllib.parse.urlencode(values),encoding=’utf8′) # 编码工作
    print(data)
    req = urllib.request.Request(url,data) # 发送请求并传输data表单
    response = urllib.request.urlopen(req)
    the_page = response.read().decode(‘utf8’) # 读取响应内容
    print(the_page)

    b’name=WHY&localtion=SDU&language=Python’
    {
    “args”: {},
    “data”: “”,
    “files”: {},
    “form”: {

    "language": "Python", 
    "localtion": "SDU", 
    "name": "WHY"
    

    },
    “headers”: {

    "Accept-Encoding": "identity", 
    "Connection": "close", 
    "Content-Length": "38", 
    "Content-Type": "application/x-www-form-urlencoded", 
    "Host": "httpbin.org", 
    "User-Agent": "Python-urllib/3.6"
    

    },
    “json”: null,
    “origin”: “112.17.235.178”,
    “url”: “”
    }

    # 直接传入data
    import urllib.parse
    import urllib.request

    data = bytes(urllib.parse.urlencode({‘word’: ‘hello’}), encoding=’utf8′)
    print(data)
    response = urllib.request.urlopen(”, data=data)
    print(response.read().decode(‘utf8’))

    b’word=hello’
    {
    “args”: {},
    “data”: “”,
    “files”: {},
    “form”: {

    "word": "hello"
    

    },
    “headers”: {

    "Accept-Encoding": "identity", 
    "Connection": "close", 
    "Content-Length": "10", 
    "Content-Type": "application/x-www-form-urlencoded", 
    "Host": "httpbin.org", 
    "User-Agent": "Python-urllib/3.6"
    

    },
    “json”: null,
    “origin”: “112.17.235.178”,
    “url”: “”
    }

    import urllib
    data = {}
    data[‘name’] = ‘WHY’
    data[‘location’] = ‘SDU’
    data[‘language’] = ‘Python’
    url_values = urllib.parse.urlencode(data)
    print(url_values)
    url = ”
    full_url = url + ‘?’+ url_values
    data = urllib.request.urlopen(full_url)
    print(data.read().decode(‘utf8’))

    name=WHY&location=SDU&language=Python








这里就用到urllib.parse,通过bytes(urllib.parse.urlencode())可以将post数据进行转换放到urllib.request.urlopendata参数中。这样就完成了一次post请求。所以如果我们添加data参数的时候就是以post请求方式请求,如果没有data参数就是get请求方式

  • timeout参数的使用
    在某些网络情况不好或者服务器端异常的情况会出现请求慢的情况,或者请求异常,所以这个时候我们需要给
    请求设置一个超时时间,而不是让程序一直在等待结果。例子如下:

    import urllib.request

    response = urllib.request.urlopen(”, timeout=1)
    print(response.read())

    b'{n “args”: {}, n “headers”: {n “Accept-Encoding”: “identity”, n “Connection”: “close”, n “Host”: “httpbin.org”, n “User-Agent”: “Python-urllib/3.6″n }, n “origin”: “218.75.27.189”, n “url”: “‘

我们需要对异常进行抓取,代码更改为

import socket
import urllib.request
import urllib.error

try:
    response = urllib.request.urlopen('http://httpbin.org/get', timeout=0.1)
except urllib.error.URLError as e:
    if isinstance(e.reason, socket.timeout):
        print('TIME OUT')

TIME OUT

响应response

响应类型、状态码、响应头
response.status、response.getheaders()、response.getheader("server"),获取状态码以及头部信息
response.read()获得的是响应体的内容

import urllib.request

response = urllib.request.urlopen('http://www.cnblogs.com/zhaof/p/6910871.html')
print(type(response))
print(response.status)
print(response.getheaders())   # Return list of (header, value) tuples.
print(response.getheader("server"))
print(response.getheader('Date'))

<class 'http.client.HTTPResponse'>
200
[('Date', 'Tue, 22 Aug 2017 12:33:22 GMT'), ('Content-Type', 'text/html; charset=utf-8'), ('Content-Length', '34644'), ('Connection', 'close'), ('Vary', 'Accept-Encoding'), ('Cache-Control', 'private, max-age=10'), ('Expires', 'Tue, 22 Aug 2017 12:33:31 GMT'), ('Last-Modified', 'Tue, 22 Aug 2017 12:33:21 GMT'), ('X-UA-Compatible', 'IE=10'), ('X-Frame-Options', 'SAMEORIGIN')]
None
Tue, 22 Aug 2017 12:33:22 GMT

发表评论

电子邮件地址不会被公开。 必填项已用*标注