0%

Python 爬虫入门

爬虫概述

网络蜘蛛(Web spider)也叫网络爬虫(Web crawler),蚂蚁(ant),自动检索工具(automatic indexer),或者(在FOAF软件概念中)网络疾走(WEB scutter),是一种“自动化浏览网络”的程序,或者说是一种网络机器人。它们被广泛用于互联网搜索引擎或其他类似网站,以获取或更新这些网站的内容和检索方式。它们可以自动采集所有其能够访问到的页面内容,以供搜索引擎做进一步处理(分检整理下载的页面),而使得用户能更快的检索到他们需要的信息。

使用爬虫抓取网页

想要使用python抓取网页,首先出场的就是urllib2,使用其urlopen方法就可以获取到一个网页信息

1
2
3
import urllib2
response = urllib2.urlopen("http://baidu.com")
print response.read()

对于urllib2库中的urlopen()方法,它可以接受三个参数
urlopen(url,data,timeout)
url即为URL,data为访问URL时要传送的数据,timeout为超时时间。后两个参数可以不添加,data默认为空None,timeout默认为socket._GLOBAL_DEFAULT_TIMEOUT
通过urlopen方法获得的信息保存到response中
通过打印response.read()可以获取网页内容

第三的参数就是timeout设置

response = urllib2.urlopen('http://sdfsdf',timeout=10)
response = urllib2.urlopen('http://sdfsdf',data,10)

Request对象

在urlopen的参数中,可以传入一个request请求,我们可以创建一个Request的实例来作为参数传给urlopen

1
2
3
4
import urllib2
request = urllib2.Request("http://www.baidu.com")
response = urllib2.urlopen(request)
print response.read()

Post请求与Get请求

POST

1
2
3
4
5
6
7
8
9
import urllib
import urllib2

values = {"usename":"12345678@123.com","password":"xxx"}
data = urllib.urlencode(values)
url = "http://urlurlurl"
request = urllib2.Request(url,data)
response = urllib2.urlopen(request)
print response.read()

还可以使用以下方式定义字典

1
2
3
4
values = {}
values['username'] = "123456@123.com"
values['password'] = "123456"
data = urllib.urlencode(values)

GET

1
2
3
4
...
data = urllib.urlencode(values)
url = "http://urlurlurl"+"?"+data
request = urllib2.Request(url)

Urllib库用法

设置Headers

在Headers里有很多的信息,如文件编码,压缩方式,请求身份等
user_agent就是请求身份
'User_Agent':'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)'
这样就设置好了一个headers,在构建request时传入,这样在请求页面是,就会与request一起发到服务器上。
另外,有的服务器设置了防盗链,服务器会识别headers中的referer是不是它自己,如果不是,服务器就不会响应请求。
'Referer':'http://urlurlurl'
另外,在headers中还有一些其他属性

User-Agent : 有些服务器或 Proxy 会通过该值来判断是否是浏览器发出的请求
Content-Type : 在使用 REST 接口时,服务器会检查该值,用来确定 HTTP Body 中的内容该怎样解析。
application/xml : 在 XML RPC,如 RESTful/SOAP 调用时使用
application/json : 在 JSON RPC 调用时使用
application/x-www-form-urlencoded : 浏览器提交 Web 表单时使用
在使用服务器提供的 RESTful 或 SOAP 服务时, Content-Type 设置错误会导致服务器拒绝服务

Proxy设置

urllib2默认会使用环境变量http_proxy设置HTTP Proxy.有些网站会检测某一段时间内某个IP的访问次数,如果访问次数过多,会禁止访问。所以可以通过设置一些代理服务器来协助你抓取页面。

Proxy的用法

1
2
3
4
5
6
7
8
9
import urllib2
enable_proxy = True
proxy_handler = urllib2.ProxyHandler({"http":"http://some-proxy.com:8080"})
null_proxy_handler = urllib2.ProxyHandler({})
if enable_proxy:
opener = urllib2.build_opener(proxy_handler)
else:
opener = urllib2.build_opener(null_proxy_handler)
urllib2.install_opener(opener)

HTTP的PUT和DELETE方法

PUT和POST极为相似,都是向服务器发送数据,但它们之间有一个重要区别,PUT通常指定了资源的存放位置,而POST则没有,POST的数据存放位置由服务器自己决定。
DELETE:删除某一个资源。很少用到。

1
2
3
4
import urllib2
request = urllib2.Request(uri, data=data)
request.get_method = lambda: 'PUT' # or 'DELETE'
response = urllib2.urlopen(request)

URLError异常处理

URLError

要想在python中捕获异常,就要用到try-except语句

1
2
3
4
5
6
7
import urllib2

requset = urllib2.Request('http://www.xxxxx.com')
try:
urllib2.urlopen(requset)
except urllib2.URLError, e:
print e.reason

HTTPError

HTTPError是URLError的子类,HTTPError实例产生后,会有一个code属性。
code属性是服务器返回的状态码
由于HTTPError的父类是URLError,因此父类的异常应该放在子类的后面

1
2
3
4
5
6
7
8
9
10
11
import urllib2

req = urllib2.Request('http://blog.csdn.net/cqcre')
try:
urllib2.urlopen(req)
except urllib2.HTTPError, e:
print e.code
except urllib2.URLError, e:
print e.reason
else:
print "OK"

如果捕获到了HTTPError,则输出code,不会再处理URLError异常。如果发生的不是HTTPError,则会去捕获URLError异常,输出错误原因。

另外还可以加入 hasattr属性提前对属性进行判断

1
2
3
4
5
6
7
8
9
10
11
12
import urllib2

req = urllib2.Request('http://blog.csdn.net/cqcre')
try:
urllib2.urlopen(req)
except urllib2.URLError, e:
if hasattr(e,"code"):
print e.code
if hasattr(e,"reason"):
print e.reason
else:
print "OK"

Cookie的使用

Opener

当你获取一个URL你使用一个opener(一个urllib2.OpenerDirector的实例)。在前面,我们都是使用的默认的opener,也就是urlopen。它是一个特殊的opener,可以理解成opener的一个特殊实例,传入的参数仅仅是url,data,timeout。

如果我们需要用到Cookie,只用这个opener是不能达到目的的,所以我们需要创建更一般的opener来实现对Cookie的设置。

Cookielib

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

CookieJar —-派生—->FileCookieJar —-派生—–>MozillaCookieJar和LWPCookieJar

获取Cookie保存到变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import urllib2
import cookielib

# 声明CookieJar保存Cookie对象
cookie = cookielib.CookieJar()

# 创建cookie处理器
handler = urllib2.HTTPCookieProcessor(cookie)

# 通过handler构建opener
opener = urllib2.build_opener(handler)

response = opener.open('http://www.baidu.com')
for item in cookie:
print 'Name='+item.name
print 'Value='+item.value

打印结果

1
2
3
4
5
6
7
8
9
10
11
12
Name=BAIDUID
Value=61D539BC6FC3780C74A3F9E76D463B12:FG=1
Name=BIDUPSID
Value=61D539BC6FC3780C74A3F9E76D463B12
Name=H_PS_PSSID
Value=19684_1433_18240_20076_19860_15268_11463
Name=PSTM
Value=1464948230
Name=BDSVRTM
Value=0
Name=BD_HOME
Value=0

保存Cookie到文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# -*- coding: utf-8 -*-

import urllib2
import cookielib

# 设置保存cookie的文件
filename = 'cookie.txt'
# 声明MozillaCookieJar保存cookie,并写入文件
cookie = cookielib.MozillaCookieJar(filename)

# 创建Cookie管理器
handler = urllib2.HTTPCookieProcessor(cookie)

# 构建opener
opener = urllib2.build_opener(handler)

# 创建请求
response = opener.open("http://www.baidu.com")
# 保存到文件
cookie.save(ignore_discard=True, ignore_expires=True)

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

从文件中获取Cookie并访问

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# -*- coding: utf-8 -*-
import urllib2
import cookielib

# 创建MozillaCookieJar
cookie = cookielib.MozillaCookieJar()
# 从文件中读取cookie
cookie.load('cookie.txt', ignore_expires=True, ignore_discard=True)
# 创建request
request = urllib2.Request("http://www.baidu.com")
# 创建opener
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cookie))
response = opener.open(request)
print response.read()

利用Cookie模拟网站登录

利用cookie模拟网站登录学校教务系统,读取课表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# -*- coding: utf-8 -*-
import urllib
import urllib2
import cookielib

# 保存cookie的文件
filename = 'cookie.txt'
# 创建cookie
cookie = cookielib.MozillaCookieJar(filename)
# 创建opener
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cookie))
# 要提交的登陆账号密码
postdata = urllib.urlencode({'USERNAME': '1341901120', 'PASSWORD': 'chuxuan123sh'})
# 登陆验证要提交的后台页面地址
loginurl = 'http://jwgl.just.edu.cn:8080/jsxsd/xk/LoginToXk'

response = opener.open(loginurl, postdata)
# 保存登陆成功的Cookie
cookie.save(ignore_discard=True, ignore_expires=True)

# 显示课表的后台页面地址
gradeUrl = 'http://jwgl.just.edu.cn:8080/jsxsd/xskb/xskb_list.do'
response = opener.open(gradeUrl)
# 显示页面html
print response.read()

正则表达式

正则表达式

正则表达式注解

贪婪模式与非贪婪模式

Python里数量词默认是贪婪的(在少数语言里也可能是默认非贪婪),总是尝试匹配尽可能多的字符;非贪婪的则相反,总是尝试匹配尽可能少的字符。例如:正则表达式”ab”如果用于查找”abbbc”,将找到”abbb”。而如果使用非贪婪的数量词”ab?”,将找到”a”。

常用非贪婪模式提取

反斜杠

使用反斜杠作为转义字符
python中可以使用r代替\
在正则表达式中如果要匹配”",需要写成\\,使用r可以写成r” \“表示。匹配使用可以写成\d,r” \d”

python Re模块

1
2
3
4
5
6
7
8
9
10
#返回pattern对象
re.compile(string[,flag])
#以下为匹配所用函数
re.match(pattern, string[, flags])
re.search(pattern, string[, flags])
re.split(pattern, string[, maxsplit])
re.findall(pattern, string[, flags])
re.finditer(pattern, string[, flags])
re.sub(pattern, repl, string[, count])
re.subn(pattern, repl, string[, count])

pattern = re.compile(r'hello')

在参数中我们传入了原生字符串对象,通过compile方法编译生成一个pattern对象,然后我们利用这个对象来进行进一步的匹配
参数flag是匹配模式,取值可以使用按位或运算符’|’表示同时生效,比如re.I|re.M

1
2
3
4
5
6
re.I(全拼:IGNORECASE): 忽略大小写(括号内是完整写法,下同)
re.M(全拼:MULTILINE): 多行模式,改变'^'和'$'的行为(参见上图)
re.S(全拼:DOTALL): 点任意匹配模式,改变'.'的行为
re.L(全拼:LOCALE): 使预定字符类 \w \W \b \B \s \S 取决于当前区域设定
re.U(全拼:UNICODE): 使预定字符类 \w \W \b \B \s \S \d \D 取决于unicode定义的字符属性
re.X(全拼:VERBOSE): 详细模式。这个模式下正则表达式可以是多行,忽略空白字符,并可以加入注释。
re.match(pattern, string[, flags])

这个方法将会从string(我们要匹配的字符串)的开头开始,尝试匹配pattern,一直向后匹配,如果遇到无法匹配的字符,立即返回None,如果匹配未结束已经到达string的末尾,也会返回None。两个结果均表示匹配失败,否则匹配pattern成功,同时匹配终止,不再对string向后匹配

match对象的的属性和方法
result.group , result.string

  1. string: 匹配时使用的文本。
  2. re: 匹配时使用的Pattern对象。
  3. pos: 文本中正则表达式开始搜索的索引。值与Pattern.match()和Pattern.seach()方法的同名参数相同。
  4. endpos: 文本中正则表达式结束搜索的索引。值与Pattern.match()和Pattern.seach()方法的同名参数相同。
  5. lastindex: 最后一个被捕获的分组在文本中的索引。如果没有被捕获的分组,将为None。
  6. lastgroup: 最后一个被捕获的分组的别名。如果这个分组没有别名或者没有被捕获的分组,将为None。

方法:

  1. group([group1, …]):
    获得一个或多个分组截获的字符串;指定多个参数时将以元组形式返回。group1可以使用编号也可以使用别名;编号0代表整个匹配的子串;不填写参数时,返回group(0);没有截获字符串的组返回None;截获了多次的组返回最后一次截获的子串。
  2. groups([default]):
    以元组形式返回全部分组截获的字符串。相当于调用group(1,2,…last)。default表示没有截获字符串的组以这个值替代,默认为None。
  3. groupdict([default]):
    返回以有别名的组的别名为键、以该组截获的子串为值的字典,没有别名的组不包含在内。default含义同上。
  4. start([group]):
    返回指定的组截获的子串在string中的起始索引(子串第一个字符的索引)。group默认值为0。
  5. end([group]):
    返回指定的组截获的子串在string中的结束索引(子串最后一个字符的索引+1)。group默认值为0。
  6. span([group]):
    返回(start(group), end(group))。
  7. expand(template):
    将匹配到的分组代入template中然后返回。template中可以使用\id或\g、\g引用分组,但不能使用编号0。\id与\g是等价的;但\10将被认为是第10个分组,如果你想表达\1之后是字符’0’,只能使用\g0。
re.search(pattern, string[, flags])

search方法与match方法极其类似,区别在于match()函数只检测re是不是在string的开始位置匹配,search()会扫描整个string查找匹配,match()只有在0位置匹配成功的话才有返回,如果不是开始位置匹配成功的话,match()就返回None。同样,search方法的返回对象同样match()返回对象的方法和属性

re.split(pattern, string[, maxsplit])

按照能够匹配的子串将string分割后返回列表。maxsplit用于指定最大分割次数,不指定将全部分割

1
2
3
4
5
6
7
import re

pattern = re.compile(r'\d+')
print re.split(pattern,'one1two2three3four4')

### 输出 ###
# ['one', 'two', 'three', 'four', '']
re.findall(pattern, string[, flags])

搜索string,以列表形式返回全部能匹配的子串

1
2
3
4
5
6
7
import re

pattern = re.compile(r'\d+')
print re.findall(pattern,'one1two2three3four4')

### 输出 ###
# ['1', '2', '3', '4']
re.finditer(pattern, string[, flags])

搜索string,返回一个顺序访问每一个匹配结果(Match对象)的迭代器

1
2
3
4
5
6
7
8
import re

pattern = re.compile(r'\d+')
for m in re.finditer(pattern,'one1two2three3four4'):
print m.group(),

### 输出 ###
# 1 2 3 4
re.sub(pattern, repl, string[, count])

使用repl替换string中每一个匹配的子串后返回替换后的字符串。
当repl是一个字符串时,可以使用\id或\g、\g引用分组,但不能使用编号0
当repl是一个方法时,这个方法应当只接受一个参数(Match对象),并返回一个字符串用于替换(返回的字符串中不能再引用分组)。
count用于指定最多替换次数,不指定时全部替换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import re

pattern = re.compile(r'(\w+) (\w+)')
s = 'i say, hello world!'

print re.sub(pattern,r'\2 \1', s)

def func(m):
return m.group(1).title() + ' ' + m.group(2).title()

print re.sub(pattern,func, s)

### output ###
# say i, world hello!
# I Say, Hello World!
re.subn(pattern, repl, string[, count])

返回 (sub(repl, string[, count]), 替换次数)

1
2
3
4
5
6
7
8
9
10
11
import re

pattern = re.compile(r'(\w+) (\w+)')
s = 'i say, hello world!'
print re.subn(pattern,r'\2 \1', s)
def func(m):
return m.group(1).title() + ' ' + m.group(2).title()
print re.subn(pattern,func, s)
### output ###
# ('say i, world hello!', 2)
# ('I Say, Hello World!', 2)