python爬虫入门
1.urllib的基本运用
注意:python3里面把python2的urllib库和urllib2库合并在一起了
(1)获取一个网页源代码和状态码:
from urllib import request
def getHtml(url):
resp=request.urlopen(url)
html=resp.read()
code=resp.getcode() #这个是用来获取请求的响应状态码的
html=html.decode('utf8') #在python3里面要使用decode()来给网页源代码编码
return html,code
当然这里出了直接用urlopen打开一个url获取网页源代码之外,有时我们的请求还需要传递参数,就好比登录,你传递了参数之后获得的结果可能就完全不一样了
(2)get请求和post请求
- 如果该请求是一个get请求,那么实现参数的传递很容易,直接在url后面拼串就可以了,这里就不多说了
- 如果该请求是一个post请求呢?下面就说一下post请求的参数传递
#首先构建一个字典用来以键值对的形式传递参数
data={}
data['username']='张三'
data['password']='12345'
from urllib import parse
#用parse模块的urlencode()函数来给data进行url编码,这时放回的结果是一个字符串
data=parse.urlencode(data)
#又对data进行字节编码,编码格式是utf8,这时候得到的data是一串字节,
#这里编码成字节主要是urlopen的data参数要求是byte
data=data.encode('utf8')
from urllib import request
url="http://www.xxx.com"
resp=request.urlopen(url,data)
(3)编码和解码以及解决乱码问题
既然这里已经涉及了编解码问题,那就简单提一下吧!
- b=str.encode(‘utf8’) 这里是编码,把字符串按照utf8的方式编码为字节流
- str=b.decode(‘utf8’) 这里是解码,把字节流按utf8的方式解码为字符串
注意:编码和解码方式必须要一样,不然就会出现所谓的乱码现象,所以决解乱码的一般方法通常也是这样的,先把乱码字符串按现在的解码方式编码为字节流,再用正确的解码方式解码为字符串,下面看一个例子:
字节流是用utf8编码的–>程序用ISO-8859-1方式解码–>结果我们就看到了乱码的字符串–>那么此时我们就应该先对这个乱码的字符串用ISO-8859-1方式解码回字节流先–>最后再用utf8方式解码成我们想要的字符串
2.添加请求头
(1)除了简单的使用urlopen之外我们其实还可以添加一下请求头,用来伪装浏览器
def getHtmlAddHead(url):
req=request.Request(url) #把url传给Request构造一个请求对象req
req.add_header('user-agent','Mozila/5.0') #为请求对象req添加请求头
resp=request.urlopen(req) #这里面用urlopen打开被封装好的请求对象req就可以了
code=resp.getcode()
html=resp.read()
html=html.decode('utf8')
return html,code
细心的朋友可能已经发现了,这里的urlopen传递的是一个req对象,但是,这时候如果我们想使用post请求,并且提交数据,那又应该怎么做呢?
req=request.Request(url,data) #其实就是这么简单,在构造req对象的时候,也把data传进去就可以了
(2)从添加请求头引入Request
既然说到了这里,那就顺便多说一下吧!其实添加请求头除了可以使用上面案例里面的req.add_header()外,还可以先构建好一个header,再像传data那样传,如下:
header={}
header['user-agent']='Mozila/5.0'
#这里的header是不需要编码成字节或者字符串的,直接以字典的形式传过去就可以了
req=request.Request(url,headers=header)
#当然如果还需要前面的data的话,就如下:
req=request.Request(url,data,header)
这里说一下Request的基本原型是这样的:Request(url,data,headers),如果参数填满或者前面的填满,是不用写参数原型加等于号的,但是中间有参数不填了就一定要写,这是是python的基础语法来的,不多说了,这就是上面那句代码为什么要加”headers=”的原因了。
(3)设置超时
这里再顺便提及一个很简单的东西吧!有时我们打开一个url,但是网站响应要很久,那么在这条线程上面,就会阻碍后面的url访问了,这时我们不妨放弃这些响应很慢的url,这里我们就可以利用urloepn的超时参数设置,也就是第三个参数timeout(单位s),如下:
resp=request.urlopen(req,timeout=10)
3.保存和读取cookie
(1)cookie的获取
上面已经简单的介绍了如何获取一个网页源代码还有如何添加一些基本的请求头,但是实际中很多时候,我们访问过一个网站之后,我们的一些访问信息就会被记录在cookie中,这时候如果我们第二次再访问的话,就可以把这些信息也发给服务器,让它知道我们曾访问过。
def getHtmlAddCookie(url):
cookie=http.cookiejar.LWPCookieJar() #定义一个LWPcookie
#构造一个请求打开器,并把上面的cookie放进去
opener=request.build_opener(request.HTTPCookieProcessor(cookie))
#然后把这个打开器安装到request里面,其实前面的代码也有打开器,不手动安装就使用默认的打开器
request.install_opener(opener)
print('访问前的cookie:',cookie)
resp=request.urlopen(url)
#访问过后,就可以获取到服务器响应回来的cookie了
print('访问后的cookie:',cookie)
html=resp.read()
html=html.decode('utf8')
return html,resp.getcode(),cookie
上面所获得的cookie可以用以下代码输出它的键值对:
for coo in cookie:
print(coo.name,':',coo.value)
(2)cookie的保存
当然,除了直接输出cookie之外,我们还需要懂得怎么去把cookie保存在磁盘里,供我们下一次访问的时候携带过去,我们访问所携带的cookie除了可以是我们通过自己的代码获取来的之外,还可以使用通过浏览器访问获取的:
def saveCookie(url):
filename='cookie.txt'
#要保存cookie的时候,所构建的cookie是MozillaCookie,如果是用来保存,则要传入一个filename
cookie=http.cookiejar.MozillaCookieJar(filename)
opener=request.build_opener(request.HTTPCookieProcessor(cookie))
request.install_opener(opener)
print('访问前的cookie:',cookie)
request.urlopen(url)
print('访问后的cookie:',cookie)
return cookie
cookie=saveCookie(url)
#下面的两个参数的意思是:ignore_discard:即使cookie将被丢弃也保存,
#ignore_discard:如果该文件中cookie已经存在,则覆盖原文件写入
cookie.save(ignore_discard=True,ignore_expires=True) #最后通过save()函数,把cookie写入磁盘
(3)cookie的读取
其实我们保存cookie没有多大的意思,结果还是要读取它,在请求的额时候携带过去访问服务器,假装我们不是第一次来
def visitLoadCookie(url,filename):
#要注意这里构建cookie也是MozillaCookie,不过我们是用来读取的所以先不用传filename
cookie=http.cookiejar.MozillaCookieJar()
#我们使用load函数来加载磁盘中的cookie,第一个参数是filename,后面两个同save函数
cookie.load(filename, ignore_discard=True, ignore_expires=True)
print(cookie)
for i in cookie:
print(i.name,':',i.value)
opener=request.build_opener(request.HTTPCookieProcessor(cookie))
request.install_opener(opener)
request.urlopen(url)
4.图片的下载
上面都是是说怎么获取一个html网页的源代码所涉及的一个简单的方法,但是有时我们的url指向的不是一个页面而是一些媒体信息,这时候正好我们对这些东西感兴趣,我们想要下载下来,比如我们对某个美女的照片刚兴趣,我们手工一张张下载又很麻烦,这时候不如写一个程序来帮我们下载更舒服啦!
def downImg(url):
str0=url
strArr=str0.split('/') #用/来把url分隔开
#用strArr的最后一个元素作为文件名 ,其实这里主要是把urlopen换成了urlretrieve,
#和参数添加一个文件名
request.urlretrieve(url, strArr[len(strArr)-1])
毕竟一个是根据url获取网页源代码,一个是根据url获取图片嘛!所以还是有很多地方不一样的
5.网页源码的分析
上面我们已经说了根据一个图片url怎么下载这个图片,其实就是一句代码,很简单,但是这个url又是怎么来的呢?难道是我们一个个复制过来的吗?显然不可能的,因为这样做还不如直接复制图片就可以了,其实我们的这些url是从一个网页的源代码里面通过程序分析得来的,那么下面我们就说一说从网页源代码里面分析图片url的一些简单的方法吧!
首先我们可以使用正则表达式来匹配符合要求的字符串就是对应的url了,这个方法很强大,只要你正则表达式写得6就可以了,不过我们在这里先不说这个,现在主要说的是使用一个叫做bs4的python第三方库来实现这部分功能,bs4库可以把网页html代码解释为一棵dom树,非常方便我们写代码,同时它可以使用很多解析内核来解析dom树,一般推荐使用lxml,也是一个第三方库。
def fromHtmlFingUrl(html,tag,className,attr,pr):
from bs4 import BeautifulSoup
soup=BeautifulSoup(html,'lxml') #把html代码传进去,用lxml内核解析
urls=set()
#查找网页源代码里面所有的class属性等于className的标签tag ,然后保存在tags列表里面
Tags=soup.find_all(tag, attrs={'class':className})
for i in range(len(Tags)):
#判断每一个tag的父元素是否是div,进一步删减
if Tags[i].parent.name=='div':
#拼凑出一个图片url,主要是获取tag的所有属性,用tag.attrs,
#放回一个字典,然后再获取属性attr的值,有时还需要一个前缀
url=pr+Tags[i].attrs[attr]
#最后把得到的url放入urls集合里面保存起来,这里用set主要的好处是可以避免重复
urls.add(url)
return urls
其实这里很多情况还是要根据网页实际情况来分析的,上面的规则只是我分析百度贴吧得到的一些规则而已,没什么道理好说的,同样不仅仅可以分析图片的url,还可以这样子去分析指向网页或其他资源的url,其实bs4库还有很多很强大的地方,总之它可以实现把网页当作一个树来看待,找某个资源,不过就是找树的某个节点罢了。
下面是一个下载某个贴吧第一页的帖子里面的照片的案例,代码如下:
'''
Created on 2018年1月6日
@author: Administrator
'''
from urllib import request
def getHtml(url):
try:
resp=request.urlopen(url)
html=resp.read()
html=html.decode('utf8')
except:
print('该网页无法打开')
html='<html></html>'
return html
def downImg(url):
str0=url
strArr=str0.split('/')
try:
request.urlretrieve(url, 'img/'+strArr[len(strArr)-1])
except:
print('图片无法下载')
def fromHtmlFingUrl(html,tag,className,attr,pr):
from bs4 import BeautifulSoup
soup=BeautifulSoup(html,'lxml')
urls=set()
Tags=soup.find_all(tag, attrs={'class':className})
for i in range(len(Tags)):
if Tags[i].parent.name=='div':
url=pr+Tags[i].attrs[attr]
urls.add(url)
return urls
def forUrlsSet(urls):
count=1
for i in urls:
html=getHtml(i)
urlsPhoto=fromHtmlFingUrl(html,'img','BDE_Image','src','')
for j in urlsPhoto:
downImg(j)
print('第',count,'张图片下载完成=====>')
count+=1
if count>100:
return
if __name__ == '__main__':
url='https://tieba.baidu.com/f?ie=utf-8&kw=%E7%88%86%E7%85%A7'
html=getHtml(url)
urls=fromHtmlFingUrl(html,'a','j_th_tit','href','https://tieba.baidu.com')
forUrlsSet(urls)
6.另外一个爬取网页源码的利器
(1)requests基础
是否有觉得用上面的urllib库来下载网页源代码或者还是图片还是有一点点不太方便呢?那么我们下面就来说一个更加方便的工具吧!它就是requests库。
需要注意:requests!=urllib.request
requests的底层是基于urllib3来实现的
最简单的get请求:
resp=requests.get('http://www.baidu.com')
#或者下面的写法,等价的
resp=requests.('get','http://www.baidu.com')
其实原理也是和上面差不多的,在这里就不多说了,直接写一些相关的代码吧!
import requests
def getDemo1(url):
kw = {'wd':'python'} #请求参数
#最好加上这个请求头,不然返回的字节流会默认用ISO-8859-1解码,那就回出现中文乱码
headers = {"User-Agent": "Mozilla/5.0"} #请求头
resp=requests.get(url,params=kw,headers=headers)
#返回源码可以使用下面两中方式:
tempStr=resp.text #因为加了请求头,返回回字符串不会乱码
tempByte=resp.content #反回字节流
print(type(tempStr))
print(type(tempByte))
def getDemo2(url):
kw = {'wd':'python'}
resp=requests.get(url,params=kw) #不加请求头,会出现乱码问题,而且获得的源码会少一些东西
tempStr=resp.text
tempByte=tempStr.encode('ISO-8859-1') #乱码问题,解决方法,编解码
import chardet
print(chardet.detect(tempByte)) #猜测字节流的编码格式
tempStr=tempByte.decode('utf8') #解码变回字符串
print(tempStr)
上面是get请求的方式,其实post请求也是差不多的,下面看一个简单的post请求:
response = requests.post("http://www.baidu.com/", data = data)
#也是有两种写法
response = requests.('post',"http://www.baidu.com/", data = data)
其实主要是把get换成post,并且把参数传输改params为data,也就差不多了
那下面就放一个例子吧!主要是登录用的。
#这里传入的url是你抓取到的登录接口
def postDemo(url):
myData={"email":"************@***.***", "password":"********"}
headers={ "User-Agent": "Mozilla/5.0"}
response = requests.post(url, data = myData, headers = headers)
print (response.text) #获取了登录之后的页面
(2)requests入门
通过requests来获取cookies确实是比较简单好用的,具体写法如下:
def cookiesDemo(url):
headers = {"User-Agent": "Mozilla/5.0"} #请求头
response = requests.get("http://www.baidu.com/",headers=headers)
#返回CookieJar对象
cookiejar = response.cookies
#将CookieJar转为字典:
cookiedict = requests.utils.dict_from_cookiejar(cookiejar)
print(cookiejar)
print(cookiedict)
既然都说到了cookies了,还能不说说session吗?熟悉网站开发的人,应该不会对session感到陌生吧!对就是和网站开发的那个session差不多,就是会话。
#这里传入的url1是你抓取到的登录接口
#这里传入的url2是你登录之后才能访问的一些页面的url
def sessionDemo(url1,url2):
# 创建session对象,可以保存Cookie值
mySession = requests.session()
# 添加headers
headers = {"User-Agent": "Mozilla/5.0"}
#添加post提交的数据,也就是登录名和密码
data = {"email":"*********@***.***", "password":"*********"}
# 发送带有data的请求,并获取登录后的Cookie,保存在session里,都是下面这句代码搞定
mySession.post(url1, data = data)
#session中包含用户登录后的Cookie值,其实主要是有sessionID,那就可以直接访问那些登录后才可以访问的页面
#这样的话,同一个session,其实就是还在同一个会话中
response = mySession.get(url2)
print(response.text)
7.异常错误
其实异常错误是走不开的一关,毕竟实际情况没有理想中那么好嘛!所以还是要懂一点点异常错误的处理方法才算是入门吧!
在爬虫里面我们最常意见的一个东西就是URLError,那产生这个东西的原因一般又是什么呢?主要有以下三个吧!
- 没有网络
- 服务器连接失败
找不到指定的服务器
最常用的处理方式就是try except语句来捕获相应的异常。
这里所说的异常主要有两个:URLError:这个异常一般是服务器不存在
HTTPError:这个异常一般是服务器存在,但你不能成功获取指定的页面,类型404错误那样
下面就写一些简单的代码吧!
from urllib import request
from urllib import error
#这里传入一个找不到服务器的url,其实乱写一个就可以了
def URLErrorDemo(url):
req= request.Request(url)
try:
request.urlopen(req, timeout=10)
#这个主要是用来捕捉不存指定服务器的异常
except error.URLError as err:
print(err)
print(err.reason)
#这里传入一个会报404错误的域名就可以了
def HTTPErrorDemo(url):
req= request.Request(url)
try:
request.urlopen(req, timeout=10)
#这个主要是用来捕捉类似404这种服务器存在但是页面找不到的异常
except error.HTTPError as err:
print(err)
print(err.code)
#这里再补充一下关于服务器不存在的异常,有时候在某些情况下,不存在的域名可能会被其他页面截获,
#然后你就会转到其他存在的页面,那这时候也是不会触发这个服务器不存在的异常的
#当然在实际中,也许你根本就不知道是哪个错误,所以一般来说,我们还是要两个一起用
#这里插入任一异常的url来测试
def DoubleDemo(url):
req = request.Request(url)
try:
request.urlopen(req)
#其实HTTPError是URLError的子类来的,子类要放在父类的前面,这样才能正常捕捉到子类的异常,
#不然的话,什么鬼异常都被父类捕捉了,那这个子类异常就废了
except error.HTTPError as e:
print(e.code)
except error.URLError as e:
print(e.reason)
else:
print('正常,执行后面的程序')
最后还找一些响应状态码的参考资料来看一下,好了解异常的原因,可能比较好些。