Python是一门十分强大的语言,要真正了解它的魅力必须通过一些实际运用·。这次通过写一个网络爬虫,我们来体验一下Python在应用中的优势。
关于爬虫
定义:爬虫是一段自动抓取互联网信息的程序。
网络蜘蛛(Web spider)也叫网络爬虫(Web crawler)[1],蚂蚁(ant),自动检索工具(automatic indexer),或者(在FOAF软件概念中)网络疾走(WEB scutter),是一种“自动化浏览网络”的程序,或者说是一种网络机器人。它们被广泛用于互联网搜索引擎或其他类似网站,以获取或更新这些网站的内容和检索方式。它们可以自动采集所有其能够访问到的页面内容,以供搜索引擎做进一步处理(分检整理下载的页面),而使得用户能更快的检索到他们需要的信息。(摘自维基百科)
爬虫实际上是一种自动访问互联网的程序,它从一个给定的URL出发,找到所有相关的超链接,生成一张待访问列表,也叫“爬行疆域”(crawl frontier).然后通过一定机制下载并储存所需要爬去的内容。
关于URI和URL
简单的来讲,URL就是在浏览器端输入的 http://www.baidu.com
这个字符串。
在理解URL之前,首先要理解URI的概念。
什么是URI?
Web上每种可用的资源,如 HTML文档、图像、视频片段、程序等都由一个通用资源标志符(Universal Resource Identifier, URI)进行定位。
URI通常由三部分组成:
①访问资源的命名机制;
②存放资源的主机名;
③资源自身 的名称,由路径表示。
如下面的URI:
http://www.why.com.cn/myhtml/html1223/
我们可以这样解释它:
①这是一个可以通过HTTP协议访问的资源,
②位于主机 www.webmonkey.com.cn上,
③通过路径“/html/html40”访问。
四、URL的理解和举例
URL是URI的一个子集。它是Uniform Resource Locator的缩写,译为“统一资源定位 符”。
通俗地说,URL是Internet上描述信息资源的字符串,主要用在各种WWW客户程序和服务器程序上。
采用URL可以用一种统一的格式来描述各种信息资源,包括文件、服务器的地址和目录等。
URL的一般格式为(带方括号[]的为可选项):
protocol :// hostname[:port] / path / [;parameters][?query]#fragment
URL的格式由三部分组成:
①第一部分是协议(或称为服务方式)。
②第二部分是存有该资源的主机IP地址(有时也包括端口号)。
③第三部分是主机资源的具体地址,如目录和文件名等。
第一部分和第二部分用“://”符号隔开,
第二部分和第三部分用“/”符号隔开。
第一部分和第二部分是不可缺少的,第三部分有时可以省略。
五、URL和URI简单比较
URI属于URL更低层次的抽象,一种字符串文本标准。
换句话说,URI属于父类,而URL属于URI的子类。URL是URI的一个子集。
URI的定义是:统一资源标识符;
URL的定义是:统一资源定位符。
二者的区别在于,URI表示请求服务器的路径,定义这么一个资源。
而URL同时说明要如何访问这个资源(http://)。
举例:
我们一起来看下面这个虚构的例子。这是一个URI:
http://bitpoetry.io/posts/hello.html#intro
我们开始分析 http://是定义如何访问资源的方式。另外 bitpoetry.io/posts/hello.html是资源存放的位置,那么,在这个例子中, #intro是资源。
URL是URI的一个子集,告诉我们访问网络位置的方式。在我们的例子中,URL应该如下所示:
http://bitpoetry.io/posts/hello.html
URN是URI的子集,包括名字(给定的命名空间内),但是不包括访问方式,如下所示:
bitpoetry.io/posts/hello.html#intro
URI可以被分为URL、URN或两者的组合。更简单地说,URI就是表示如何寻找一个人这个问题,而URL和·URN就是通过地址或者通过公司名来找一个人(可能有重叠),这是个人的理解。
参考: 维基百科—网络蜘蛛: https://zh.wikipedia.org/wiki/%E7%B6%B2%E8%B7%AF%E8%9C%98%E8%9B%9B
Python爬虫学习:http://blog.csdn.net/pleasecallmewhy/article/details/8922826
urllib和urllib2
urllib2的urlopen方法使用:urlopen(url, data, timeout)
mport urllib2
response = urllib2.urlopen("http://www.baidu.com")
print response.read()
第一个参数url即为URL,第二个参数data是访问URL时要传送的数据,第三个timeout是设置超时时间。
第二三个参数是可以不传送的,data默认为空None,timeout默认为 socket._GLOBAL_DEFAULT_TIMEOUT
第一个参数URL是必须要传送的,在这个例子里面我们传送了百度的URL,执行urlopen方法之后,返回一个response对象,返回信息便保存在这里面。response对象有read方法,调用后可以显示抓取的内容
urlopen参数可以传入一个request请求,它其实就是一个Request类的实例,构造时需要传入Url,Data等等的内容。比如上面的两行代码,我们可以这么改写
import urllib2
request = urllib2.Request("http://www.baidu.com")
response = urllib2.urlopen(request)
print response.read()
POST和GET
区别:GET方式是直接以链接形式访问,链接中包含了所有的参数,当然如果包含了密码的话是一种不安全的选择,不过你可以直观地看到自己提交了什么内容。POST则不会在网址上显示所有的参数,因此也不会那么直观。
例如:
import urllib
import urllib2
values = {"username":"XXX","password":"XXX"}
data = urllib.urlencode(values)
url = "https://passport.csdn.net/account/login?from=http://my.csdn.net/my/mycsdn"
request = urllib2.Request(url,data)
response = urllib2.urlopen(request)
print response.read()
以上是一种POST方法,传入URL和data构造request,返回POST之后的响应内容。
再如:
```
import urllib
import urllib2
values = {}
values['username'] = 'xxx'
values['password'] = 'xxx'
data = urllib.urlencode(values)
url = "https://passport.csdn.net/account/login"
get_url = url + "?" + data
request = urllib2.Request(get_url)
response = urllib2.urlopen(request)
print response.read()
设置headers
当我们访问一个网站时,需要一些必要信息来标明一些重要的需求,这个部分叫做消息头。
详细的字段列表可参考:https://zh.wikipedia.org/wiki/HTTP%E5%A4%B4%E5%AD%97%E6%AE%B5%E5%88%97%E8%A1%A8
这里介绍一下http协议相关内容:
http:hypertext transport protocol的缩写,基于TCP/IP协议来传输数据(HTML,图片等),建立于C/S架构之上的无连接,媒体独立,无状态的协议。(无连接指的是一次请求连一次,收到客户端答复后会断开,下次再请求则重新连接;媒体独立指的是只要客户端和服务器知道如何处理数据,则任何类型的数据都可以传;无状态指的是没有记忆力,在需要之前已经访问过的信息时需要再请求一遍)
- 客户端发送一个HTTP请求到服务器的请求消息包括以下格式:请求行(request line)、请求头部(header)、空行和请求数据四个部分组成,下表给出了请求报文的一般格式。
请求方式 | 空格 | URL | 空格 | 协议版本 |
---|---|---|---|---|
头部字段名 | : | 值 | 回车换行 | |
……..(同上) ……… | ||||
数据 | 数据 | 数据 | 数据 | 数据 |
HTTP响应也由四个部分组成,分别是:状态行、消息报头、空行和响应正文。
示例:
客户端请求:
GET /hello.txt HTTP/1.1
User-Agent: curl/7.16.3 libcurl/7.16.3 OpenSSL/0.9.7l zlib/1.2.3
Host: www.example.com
Accept-Language: en, mi
服务端响应:
HTTP/1.1 200 OK
Date: Mon, 27 Jul 2009 12:28:53 GMT
Server: Apache
Last-Modified: Wed, 22 Jul 2009 19:15:56 GMT
ETag: "34aa387-d-1568eb00"
Accept-Ranges: bytes
Content-Length: 51
Vary: Accept-Encoding
Content-Type: text/plain
那如何用Python的urllib2设置headers呢?来看个示例:
import urllib
import urllib2
url = 'http://www.server.com/login'
user_agent = 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)'
values = {'username' : 'xxx', 'password' : 'XXX' }
headers = { 'User-Agent' : user_agent }
data = urllib.urlencode(values)
request = urllib2.Request(url, data, headers)
response = urllib2.urlopen(request)
page = response.read()
这里设置了用户身份,其他的响应头信息也可以类似地添加,最主要的是我们可以先构造一个消息头字典,然后传入urllib2的Request构造函数中,这样就可以添加消息头了。扩展示例:
我们还有对付”防盗链”的方式,对付防盗链,服务器会识别headers中的referer是不是它自己,如果不是,有的服务器不会响应,所以我们还可以在headers中加入referer
headers = { 'User-Agent' : 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)' ,
'Referer':'http://www.zhihu.com/articles' }
同上面的方法,在传送请求时把headers传入Request参数里,这样就能应付防盗链了。
设置代理和Timeout
先看一段示例:
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)
这里首先定义了一个bool类型的变量,用于标志是否开启代理,然后调用urllib2的ProxyHandler方法,传入一个字典,该字典value为协议名称,key为具体代理域名,如果有不同协议的代理,需设置不同字典值:
proxy_handler = urllib2.ProxyHandler({"http" : 'http://some-proxy.com:8080',"https": "https:xxx:xxx"})
还有就是build_opener
方法,这里引用一段解释:
urllib2.install_opener(opener)和urllib2.build_opener([handler, …])
install_opener和build_opener这两个方法通常都是在一起用,也有时候build_opener单独使用来得到OpenerDirector对象。
install_opener实例化会得到OpenerDirector 对象用来赋予全局变量opener。如果想用这个opener来调用urlopen,那么就必须实例化得到OpenerDirector;这样就可以简单的调用OpenerDirector.open()来代替urlopen()。
build_opener实例化也会得到OpenerDirector对象,其中参数handlers可以被BaseHandler或他的子类实例化。子类中可以通过以下实例化:ProxyHandler (如果检测代理设置用), UnknownHandler, HTTPHandler, HTTPDefaultErrorHandler, HTTPRedirectHandler, FTPHandler, FileHandler, HTTPErrorProcessor。
import urllib2
req = urllib2.Request('http://www.python.org/')
opener=urllib2.build_opener()
urllib2.install_opener(opener)
f = opener.open(req)
如上使用 urllib2.install_opener()设置 urllib2 的全局 opener。这样后面的使用会很方便,但不能做更细粒度的控制,比如想在程序中使用两个不同的 Proxy 设置等。比较好的做法是不使用 install_opener 去更改全局的设置,而只是直接调用 opener的open 方法代替全局的 urlopen 方法。
关于timeout:
import urllib2
response = urllib2.urlopen('http://www.baidu.com', timeout=10)
这个之前介绍urlopen时就说过有这个参数可选了,这个主要是应对网络较慢的情况,设置多久超时。
使用PUT/DELETE
import urllib2
request = urllib2.Request(uri, data=data)
request.get_method = lambda: 'PUT' # or 'DELETE'
response = urllib2.urlopen(request)
这个不常用,主要是了解这里可以用request的获取方法的方法来使用http的另外两种请求方式。
使用DEBUG LOG
import urllib2
httpHandler = urllib2.HTTPHandler(debuglevel=1)
httpsHandler = urllib2.HTTPSHandler(debuglevel=1)
opener = urllib2.build_opener(httpHandler, httpsHandler)
urllib2.install_opener(opener)
response = urllib2.urlopen('http://www.baidu.com')
通过以上方法把 Debug Log 打开,这样收发包的内容就会在屏幕上打印出来,方便调试。
使用Cookie
Cookie,指某些网站为了辨别用户身份、进行session跟踪而储存在用户本地终端上的数据(通常经过加密)
比如说有些网站需要登录后才能访问某个页面,在登录之前,你想抓取某个页面内容是不允许的。那么我们可以利用Urllib2库保存我们登录的Cookie,然后再抓取其他页面就达到目的了。
opener
当你获取一个URL你使用一个opener(一个urllib2.OpenerDirector的实例)。在前面,我们都是使用的默认的opener,也就是urlopen。它是一个特殊的opener,可以理解成opener的一个特殊实例,传入的参数仅仅是url,data,timeout。
如果我们需要用到Cookie,只用这个opener是不能达到目的的,所以我们需要创建更一般的opener来实现对Cookie的设置。
Cookielib
cookielib模块的主要作用是提供可存储cookie的对象,以便于与urllib2模块配合使用来访问Internet资源。Cookielib模块非常强大,我们可以利用本模块的CookieJar类的对象来捕获cookie并在后续连接请求时重新发送,比如可以实现模拟登录功能。该模块主要的对象有CookieJar、FileCookieJar、MozillaCookieJar、LWPCookieJar。
它们的关系:CookieJar —-派生—->FileCookieJar —-派生—–>MozillaCookieJar和LWPCookieJar
获取Cookie保存到变量
首先,我们先利用CookieJar对象实现获取cookie的功能,存储到变量中,先来感受一下
import urllib2
import cookielib
#声明一个CookieJar对象实例来保存cookie
cookie = cookielib.CookieJar()
#利用urllib2库的HTTPCookieProcessor对象来创建cookie处理器
handler=urllib2.HTTPCookieProcessor(cookie)
#通过handler来构建opener
opener = urllib2.build_opener(handler)
#此处的open方法同urllib2的urlopen方法,也可以传入request
response = opener.open('http://www.baidu.com')
for item in cookie:
print 'Name = '+item.name
print 'Value = '+item.value
保存Cookie到文件
在上面的方法中,我们将cookie保存到了cookie这个变量中,如果我们想将cookie保存到文件中该怎么做呢?这时,我们就要用到FileCookieJar这个对象了,在这里我们使用它的子类MozillaCookieJar来实现Cookie的保存
import cookielib
import urllib2
#设置保存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(handler)
#创建一个请求,原理同urllib2的urlopen
response = opener.open("http://www.baidu.com")
#保存cookie到文件
cookie.save(ignore_discard=True, ignore_expires=True)
关于最后save方法的两个参数在此说明一下:
官方解释如下:
ignore_discard: save even cookies set to be discarded.
ignore_expires: save even cookies that have expiredThe file is overwritten if it already exists
由此可见,ignore_discard的意思是即使cookies将被丢弃也将它保存下来,ignore_expires的意思是如果在该文件中cookies已经存在,则覆盖原文件写入,在这里,我们将这两个全部设置为True。运行之后,cookies将被保存到cookie.txt文件中,如图:
从文件中获取Cookie并访问
那么我们已经做到把Cookie保存到文件中了,如果以后想使用,可以利用下面的方法来读取cookie并访问网站
import urllib2
import cookielib
#创建MozillaCookieJar实例对象
cookie = cookielib.MozillaCookieJar()
#从文件中读取cookie内容到变量
cookie.load('cookie.txt', ignore_discard=True, ignore_expires=True)
#创建请求的request
req = urllib2.Request("http://www.baidu.com")
#利用urllib2的build_opener方法创建一个opener
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cookie))
response = opener.open(req)
print response.read()
这里先用load装载文件,然后用该cookie构造opener,最后处理request,将cookielib和urllib2结合,cookie写入和读取结合,使得一些需要登录才能进行的爬虫迎刃而解。
附上去年爬教务处成绩的代码以供参考(当时还不会python):
#coding=utf-8
import sys
import xlrd
import xlwt
from selenium import webdriver
from bs4 import BeautifulSoup
reload(sys)
sys.setdefaultencoding('utf-8')
phantomjs_path = 'D:\phantomjs-2.1.1-windows\\bin\phantomjs.exe'
driver = webdriver.PhantomJS(phantomjs_path)
URL = 'http://sso.jwc.whut.edu.cn/Certification//login.do'
driver.get(URL)
# 模拟登陆
userName_tag = driver.find_element_by_name('userName')
password_tag = driver.find_element_by_name('password')
# 输入学号和密码
userName_tag.send_keys("xxxxxxxxx")
password_tag.send_keys('xxxxxxxxxx')
driver.find_element_by_id('imageField').submit()
driver.find_element_by_css_selector("a[title=\"成绩、绩点查询\"]").click()
driver.find_element_by_link_text('历史成绩查询').click() # 点击历史成绩查询
driver.implicitly_wait(5) #隐式等待
n = driver.find_element_by_id('lscj_xn')
n.find_element_by_xpath("//option[@value='2015-2016']").click()
driver.find_element_by_xpath("//*[@id='navTab']/div[2]/div[2]/div[1]/form/div/table/tbody/tr/td[6]/input").submit()
print driver.find_element_by_id('undefined').text
data = driver.find_element_by_id('undefined').get_attribute('innerHTML')
soup = BeautifulSoup(data,"html.parser")
tr_tag_l1 = [td.get_text() for td in soup.find_all('td')]
wbk = xlwt.Workbook()
sheet = wbk.add_sheet('sheet 1')
a = 0 # 计算已写个数,用来换行
t = 0 # 记录一共有几行
for b in tr_tag_l1:
sheet.write(t, a, b)
a=a+1
if a==11:
# 换到下一行,第一列
a=0
t=t+1
wbk.save('GPA.xls')
# 读取 excel 文件
wb=xlrd.open_workbook('GPA.xls')
# 找到相应的 sheet 表单
sh = wb.sheet_by_name('sheet 1')
# 对表中的元素进行计算操作
score_point_sum = 0 # 绩点×学分的和
point_sum = 0 # 学分总和
line=1 # 行数
# 在上面统计的总行数 t 之内提取数据计算
while line < t :
score_point_sum = score_point_sum + float(sh.cell(line,10).value)* float(sh.cell(line,4).value)
point_sum = point_sum + float(sh.cell(line,4).value)
line=line+1
# 计算 GPA 并写入 excel 表格
GPA = score_point_sum/point_sum
sheet.write(t+1,0,'GPA')
sheet.write(t+1,1,GPA)
print 'GPA:'
print GPA
wbk.save('GPA.xls')
说实话,没学python,靠着网上的资料完成了这个爬取任务,我都感觉不可思议。这也从侧面说明Python的易用性。附上一个简单版的:
import urllib
import urllib2
import cookielib
filename = 'cookie.txt'
#声明一个MozillaCookieJar对象实例来保存cookie,之后写入文件
cookie = cookielib.MozillaCookieJar(filename)
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cookie))
postdata = urllib.urlencode({
'stuid':'xxx',
'pwd':'xxx'
})
#登录教务系统的URL
loginUrl = 'http://jwxt.sdu.edu.cn:7890/pls/wwwbks/bks_login2.login'
#模拟登录,并把cookie保存到变量
result = opener.open(loginUrl,postdata)
#保存cookie到cookie.txt中
cookie.save(ignore_discard=True, ignore_expires=True)
#利用cookie请求访问另一个网址,此网址是成绩查询网址
gradeUrl = 'http://jwxt.sdu.edu.cn:7890/pls/wwwbks/bkscjcx.curscopre'
#请求访问成绩查询网址
result = opener.open(gradeUrl)
print result.read()
正则表达式
此处可参考Python正则表达式指南
由于最近还有一些其他任务,关于正则表达式部分只能先放一放了。正则表达式博大精深,后面我再抽个时间深入学习一下吧,希望今日所写对大家有所帮助。
参考: 网络蜘蛛-维基百科:(https://zh.wikipedia.org/wiki/%E7%B6%B2%E8%B7%AF%E8%9C%98%E8%9B%9B)
爬虫入门:http://cuiqingcai.com/954.html
urllib和urllib2: http://www.cnblogs.com/wly923/archive/2013/05/07/3057122.html