前言
1、什么是爬虫和爬虫的基本逻辑
“爬虫”是一种形象的说法。互联网比喻成一张大网,爬虫是一个程序或脚本在这种大网上爬走。碰到虫子(资源),若是所需的资源就获取或下载下来。这个资源通常是网页、文件等等。可以通过该资源里面的url链接,顺藤摸瓜继续爬取这些链接的资源。
你也可以把爬虫当作模拟我们正常上网。打开网页并分析网页的内容获取我们想要的东西。
那么,这里就涉及到http传输协议等相关的知识。
我们通常打开一个网页,基本上都是打开一个Url链接即可。在这个过程当中,实际上发生了很多事情。
打开一个Url链接,浏览器自动向Url链接的服务器发送一个请求(Request),告诉服务器说我需要访问这个Url链接的内容,请返回数据给我。服务器就处理该请求,响应该请求并返回结果给浏览器。
既然爬虫需要模拟该过程。根据http协议,爬虫需要构造一个请求(Request),发到请求到目标服务器(通常是Url链接)。然后等待服务器的响应(Response)。
所有相关的数据都在这个响应结果当中,这个就是爬虫实现的基本逻辑。
2、urllib2实现GET请求
GET和POST是请求中最常见的两种方式。(一共有6种)
GET方式是通过Url链接的方式传输相关的参数或数据。一般打开网址是GET方式请求,例如打开百度首页、谷歌首页。
有时候,需要向这个链接传输一些参数。
例如我在百度搜索一个词,发现链接变成 https://www.baidu.com/s?ie=UTF-8&wd=测试
这里有个?问号以及后面一堆数据。问号后面的数据是GET请求的参数,这里一共有两组参数。
1)ie = UTF-8
2)wd = 测试
每组参数用&符号链接。在参数中,等号前面的是参数名;等号后面的是参数值。
例如第2组参数的含义是百度搜索关键字为“测试”。第1组参数是设置返回ie浏览器的编码格式,可有可无,作为说明加入进来。
那么,我使用urllib2模拟百度搜索代码如下:
#coding:utf-8
import urllib, urllib2
#前半部分的链接(注意是http,不是https)
url_pre = 'http://www.baidu.com/s'
#GET参数
params = {}
params['wd'] = u'测试'.encode('utf-8')
url_params = urllib.urlencode(params)
#GET请求完整链接
url = '%s?%s' % (url_pre, url_params)
#打开链接,获取响应
response = urllib2.urlopen(url)
#获取响应的html
html = response.read()
#将html保存到文件
with open('test.txt', 'w') as f:
f.write(html)
执行代码,可以看到爬取的内容。
上面的链接之所以是http,不是https。因为百度在https做了重定向,重定向到http的链接。
若直接获取https的链接内容是获取不到什么数据。
另外,后面我们会讲解如何处理html的内容,怎么从html中获取我们所需的数据。
好了,先讲解一下代码。
在合并Url链接的时候,使用了urllib库的urlencode方法。该方法是将字典组成x1=a&x2=b的形式。
再看第9行代码,我使用encode方法是为了处理unicode字符串为string字符串。若不做该处理,urlencode方法无法使用。
以上这些都是一些不得不讲的细节,整段代码的核心是第16行和第19行。
第16行是使用urlopen方法打开一个Url链接,发送请求并等待响应;
第19行是从响应(Response)中获取网页的html代码即网页内容。
urlopen方法还有其他参数,格式如下:
urllib2.urlopen(url, data, timeout)
后面两个参数是选填的,data参数是接下来要讲的POST请求所使用到的参数;timeout参数是设置超时时间。
为了避免过长时间未响应,可以使用timeout设置。
urlopen方法还有另外一种格式:
urllib2.urlopen(request, data, timeout)
我比较倾向使用这个方式,逻辑清晰,而且还可以对request做其他设置。上面百度搜索代码可以改成如下:
#coding:utf-8
import urllib, urllib2
#前半部分的链接(注意是http,不是https)
url_pre = 'http://www.baidu.com/s'
#GET参数
params = {}
params['wd'] = u'测试'.encode('utf-8')
url_params = urllib.urlencode(params)
#GET请求完整链接
url = '%s?%s' % (url_pre, url_params)
#构造请求,获取响应
request = urllib2.Request(url)
response = urllib2.urlopen(request)
#获取响应的html
html = response.read()
with open('test.txt', 'w') as f:
f.write(html)
3、urllib2实现POST请求
POST请求通常是Form表单提交数据常用的请求方式。
相对GET请求来说,POST请求可以向服务器传递更多的数据。这点是它们之间主要的区别。
通常出现在登录、提交表单、向API接口发送JSON数据这几种情况。
这里需要借助一些辅助工具帮助我们查看浏览器和服务器之间的小动作。
例如在浏览器按F12调试或Fiddler,这些又是一大块知识。大家可以自行搜索和学习。
有关POST请求代码和GET请求差不多。这里没有较好的简单实例,因为很多网址对POST先加密再发送或者加了验证码等等处理。
简单写个表单提交的POST请求代码示例代码,了解一下:
#coding:utf-8
import urllib, urllib2
#构造表单数据,表单数据也是和GET请求一样的形式
values = {}
values['username'] = "aaaaaa"
values['password'] = "bbbbbb"
data = urllib.urlencode(values)
#构造请求
url = "http://xxxxxxxxxxx"
request = urllib2.Request(url, data)
#响应结果
response = urllib2.urlopen(request)
html = response.read()
print(html)
4、urllib2处理Cookie
http协议还有一个特性:无状态。
因为http协议是处理请求并响应结果,服务器并不知道客户端处于什么状态。例如客户端是否已经登录、客户端访问该Url链接之前打开了什么网页等等。面对这种情况,Cookie就诞生了。
Cookie记录了一些简单的数据。而每个Cookie都有有效期。我们可以标记该客户端当前的状态,例如是否登录、之前查询过什么东西、打开过该网站什么网页等等。
(ps:Cookie是保存在客户端,服务端还有一个差不多的东西:Session,可自行了解。)
urllib2可以获取和记录Cookie。这里需要构造一个opener,不是使用urllib2.urlopen。
urllib2.urlopen也是一个特殊的opener,但我们无法从中获取Cookie。如下代码:
#coding:utf-8
import urllib2
import cookielib
#创建cookie
cookie = cookielib.CookieJar()
handler=urllib2.HTTPCookieProcessor(cookie)
#通过handler来构建自定义opener
opener = urllib2.build_opener(handler)
#此处的open方法同urllib2的urlopen方法
request = urllib2.Request('http://www.baidu.com')
response = opener.open(request)
for item in cookie:
print('%s = %s' % (item.name, item.value))
自定义一个opener,获取Cookie如下:
我们还可以将Cookie保存到文件,下次需要使用的时候,再读取出来。让服务器误以为我们还在之前的某种状态。
#coding:utf-8
import urllib2
import cookielib
#设置保存cookie的文件路径
filename = 'cookie.txt'
#使用MozillaCookieJar创建cookie对象
cookie = cookielib.MozillaCookieJar(filename)
handler = urllib2.HTTPCookieProcessor(cookie)
#通过handler来构建opener
opener = urllib2.build_opener(handler)
#创建请求,同urllib2的urlopen
response = opener.open("http://www.baidu.com")
cookie.save(ignore_discard=True, ignore_expires=True)
其中,save方法的参数含义如下:
1)ignore_discard是即使cookies将被丢弃,也将其保存下来。
2)ignore_expires是如果在该文件中cookies已经存在,则覆盖写入。
读取Cookie代码如下:
#coding:utf-8
import urllib2
import cookielib
#使用MozillaCookieJar创建cookie对象
cookie = cookielib.MozillaCookieJar()
cookie.load('cookie.txt', ignore_discard=True, ignore_expires=True)
handler = urllib2.HTTPCookieProcessor(cookie)
#通过handler来构建opener
opener = urllib2.build_opener(handler)
#创建请求,同urllib2的urlopen
request = urllib2.Request("http://www.baidu.com")
response = opener.open(request)
5、反爬虫设置header
有些服务器为了避免被爬虫,会检查header。header是发送请求的时候,一起发送给服务器的数据。可以通过header得到浏览器的类型,手机端还是电脑端访问,以及从什么地方进入该链接等等。
若发现不是正常浏览器访问,服务器则直接拒绝。
so~ 我们需要进一步模拟浏览器的行为,需要模拟设置header。
#coding:utf-8
import urllib, urllib2
#设置header
user_agent = 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)'
headers = {'User-Agent':user_agent}
#构造Request请求,其中第二个参数是data
url = 'http://www.server.com/login'
request = urllib2.Request(url, None, headers)
#响应请求
response = urllib2.urlopen(request)
html = response.read()
同样,若你不知道如何设置header,可以通过抓包软件获取,例如Fiddler。
6、解析html
前面说了这么多,都是为了获取网页内容html。既然获取到html之后,我们解析?从中提取我们需要的数据?
我们所获取的html本质是字符串。处理字符串最基本的方法是通过相关的字符串函数,但效率很低,容易出错。
还可以使用正则表达式处理字符串。这部分的知识也是很多,大家可以自行了解。
这里,我想给大家说的处理方式是使用BeautifulSoup。
BeautifulSoup是解析html/xml的库。非Python自带的库,安装如下:
pip install beautifulsoup4
pip install lxml
安装lxml库是为了加快html解析效率。
先我们设置1个html内容,使用BeautifulSoup解析方法如下:
#coding:utf-8
from bs4 import BeautifulSoup
#先随便假设一个html
html = '''<html>
<head></head>
<body>
<p id="test_p" class="test">test1</p>
<p class="test">test2</p>
</body>
<html>'''
#使用lxml解析html
soup = BeautifulSoup(html, 'lxml')
soup是解析得到的解析器。我们可以根据html的结构获取对应的节点。例如我想获取p标签:
p = soup.body.p
但该方法只能获取到第1个节点。假如body标签下有很多p节点,该方法无法获取全部。
这里,我们可以用find_all或select方法获取。建议大家使用select方法,这个方法可以jQuery选择器用法差不多。例如:
p1 = soup.select('p') #获取p标签
p2 = soup.select('#test_p') #获取id为test_p的标签
p3 = soup.select('.test') #获取class为test的标签
p4 = soup.select('body .test') #获取body下的class为test的标签
来个完整的代码,输出结果:
#coding:utf-8
from bs4 import BeautifulSoup
#先随便假设一个html
html = '''<html>
<head></head>
<body>
<p id="test_p" class="test">test1</p>
<p class="test">test2</p>
</body>
<html>'''
#使用lxml解析html
soup = BeautifulSoup(html, 'lxml')
#获取全部p标签
for p in soup.select('p'):
print(p)
通过该方法,可以输出全部p标签。
那假如我要获取p标签的属性和数据呢?方法如下:
for p in soup.select('p'):
print(p.name) #标签名称
#标签属性,也可以用p['id']。若属性不存在会报错,和字典获取键值类似
print(p.get('id'))
print(p.string) #标签内容
若一个标签里面有很多子标签,你可以再进一步继续使用select。
若想获取标签下全部子标签的文本内容。可以用strings属性得到一个生成器,不过可能有很多回车和空格。若想屏蔽回车和空格,可以使用stripped_strings属性。如下所示:
print(''.join(soup.body.strings))
print(''.join(soup.body.stripped_strings))
将分别得到:
u'\ntest1\ntest2\n'
u'test1test2'
7、综合实例
讲了这么多,来个实例:获取百度搜索结果。
输入关键词,爬取百度搜索第1页的结果。我们可以先随便搜索一个词,看看html有什么特征。
发现搜索的到内容都在class为t的标签下,分别可以获取到文本内容和链接。完整代码如下:
#coding:utf-8
import urllib, urllib2
from bs4 import BeautifulSoup
import codecs
def get_contents(word):
#前半部分的链接(注意是http,不是https)
url_pre = 'http://www.baidu.com/s'
#GET参数
params = {}
params['pn'] = 20 #设置这个每页可以获取10个内容
#若是unicode编码,转成str
if isinstance(word, unicode):
params['wd'] = word.encode('utf-8')
url_params = urllib.urlencode(params)
#GET请求完整链接
url = '%s?%s' % (url_pre, url_params)
#打开链接,获取响应
request = urllib2.Request(url)
response = urllib2.urlopen(request)
#获取响应的html
html = response.read()
#解析内容
soup = BeautifulSoup(html, 'lxml')
items = soup.select('.t a')
#写入文件
f = codecs.open('test.txt', 'w', 'utf-8')
for item in items:
f.write('%s\r\n' % ''.join(item.stripped_strings))
f.write('%s\r\n\r\n' % item['href'])
f.close()
if __name__ == '__main__':
get_contents(u'测试')
运行得到结果如下:
这里我还使用了codecs模块,保存utf-8格式的文本文件。