python3之模块urllib
urllib是python内置的HTTP请求库,无需安装即可使用,它包含了4个模块:
request:它是最基本的http请求模块,用来模拟发送请求
error:异常处理模块,如果出现错误可以捕获这些异常
parse:一个工具模块,提供了许多URL处理方法,如:拆分、解析、合并等
robotparser:主要用来识别网站的robots.txt文件,然后判断哪些网站可以爬
1、urllib.request.urlopen()
urllib.request.urlopen(url,data=None,[timeout,],cafile=None,capath=None,cadefault=False,context=None)
请求对象,返回一个HTTPResponse类型的对象,包含的方法和属性:
方法:read()、readinto()、getheader(name)、getheaders()、fileno()
属性:msg、version、status、reason、bebuglevel、closed
import urllib.request
response=urllib.request.urlopen('https://www.python.org')
#请求站点获得一个HTTPResponse对象
#print(response.read().decode('utf-8')) #返回网页内容
#print(response.getheader('server')) #返回响应头中的server值
#print(response.getheaders()) #以列表元祖对的形式返回响应头信息
#print(response.version) #返回版本信息
#print(response.status) #返回状态码200,404代表网页未找到
#print(response.debuglevel) #返回调试等级
#print(response.closed) #返回对象是否关闭布尔值
#print(response.geturl()) #返回检索的URL
#print(response.info()) #返回网页的头信息
#print(response.getcode()) #返回响应的HTTP状态码
#print(response.msg) #访问成功则返回ok
#print(response.reason) #返回状态信息
urlopen()方法可传递参数:
url:网站地址,str类型,也可以是一个request对象
data:data参数是可选的,内容为字节流编码格式的即bytes类型,如果传递data参数,urlopen将使用Post方式请求
from urllib.request import urlopen
import urllib.parse
data = bytes(urllib.parse.urlencode({'word':'hello'}),encoding='utf-8')
#data需要字节类型的参数,使用bytes()函数转换为字节,使用urllib.parse模块里的urlencode()方法来讲参数字典转换为字符串并指定编码
response = urlopen('http://httpbin.org/post',data=data)
print(response.read())
#output
b'{
"args":{},"data":"","files":{},
"form":{"word":"hello"}, #form字段表明模拟以表单的方法提交数据,post方式传输数据"headers":{"Accept-Encoding":"identity",
"Connection":"close",
"Content-Length":"10",
"Content-Type":"application/x-www-form-urlencoded",
"Host":"httpbin.org",
"User-Agent":"Python-urllib/3.5"},"json":null,"origin":"114.245.157.49","url":"http://httpbin.org/post"}
'
timeout参数:用于设置超时时间,单位为秒,如果请求超出了设置时间还未得到响应则抛出异常,支持HTTP,HTTPS,FTP请求
import urllib.request
response=urllib.request.urlopen('http://httpbin.org/get',timeout=0.1) #设置超时时间为0.1秒,将抛出异常print(response.read())
#output
urllib.error.URLError: <urlopen error timed out>
#可以使用异常处理来捕获异常import urllib.requestimport urllib.errorimport sockettry:
response=urllib.request.urlopen('http://httpbin.org/get',timeout=0.1)
print(response.read())except urllib.error.URLError as e:
if isinstance(e.reason,socket.timeout): #判断对象是否为类的实例
print(e.reason) #返回错误信息#output
timed out
其他参数:context参数,必须是ssl.SSLContext类型,用来指定SSL设置,此外,cafile和capath这两个参数分别指定CA证书和它的路径,会在https链接时用到。
2、urllib.request.Requset()
urllib.request.Request(url,data=None,headers={},origin_req_host=None,unverifiable=False,method=None)
参数:
url:请求的URL,必须传递的参数,其他都是可选参数
data:上传的数据,必须传bytes字节流类型的数据,如果它是字典,可以先用urllib.parse模块里的urlencode()编码
headers:它是一个字典,传递的是请求头数据,可以通过它构造请求头,也可以通过调用请求实例的方法add_header()来添加
例如:修改User_Agent头的值来伪装浏览器,比如火狐浏览器可以这样设置:
{'User-Agent':'Mozilla/5.0 (compatible; MSIE 5.5; Windows NT)'}
origin_req_host:指请求方的host名称或者IP地址
unverifiable:表示这个请求是否是无法验证的,默认为False,如我们请求一张图片如果没有权限获取图片那它的值就是true
method:是一个字符串,用来指示请求使用的方法,如:GET,POST,PUT等
#!/usr/bin/env python
#coding:utf8from urllib import request,parse
url='http://httpbin.org/post'
headers={
'User-Agent':'Mozilla/5.0 (compatible; MSIE 5.5; Windows NT)',
'Host':'httpbin.org'
} #定义头信息
dict={'name':'germey'}
data = bytes(parse.urlencode(dict),encoding='utf-8')
req = request.Request(url=url,data=data,headers=headers,method='POST')
#req.add_header('User-Agent','Mozilla/5.0 (compatible; MSIE 8.4; Windows NT') #也可以request的方法来添加
response = request.urlopen(req)
print(response.read())
3、urllib.request的高级类
在urllib.request模块里的BaseHandler类,他是所有其他Handler的父类,他是一个处理器,比如用它来处理登录验证,处理cookies,代理设置,重定向等
它提供了直接使用和派生类使用的方法:
add_parent(director):添加director作为父类
close():关闭它的父类
parent():打开使用不同的协议或处理错误
defautl_open(req):捕获所有的URL及子类,在协议打开之前调用
Handler的子类包括:
HTTPDefaultErrorHandler:用来处理http响应错误,错误会抛出HTTPError类的异常
HTTPRedirectHandler:用于处理重定向
HTTPCookieProcessor:用于处理cookies
ProxyHandler:用于设置代理,默认代理为空
HTTPPasswordMgr:永远管理密码,它维护用户名和密码表
HTTPBasicAuthHandler:用户管理认证,如果一个链接打开时需要认证,可以使用它来实现验证功能
OpenerDirector类是用来处理URL的高级类,它分三个阶段来打开URL:
在每个阶段中调用这些方法的顺序是通过对处理程序实例 进行排序来确定的;每个使用此类方法的程序都会调用protocol_request()方法来预处理请求,然后调用protocol_open()来处理请求,最后调用protocol_response()方法来处理响应。
之前的urlopen()方法就是urllib提供的一个Opener,通过Handler处理器来构建Opener实现Cookies处理,代理设置,密码设置等
Opener的方法包括:
add_handler(handler):添加处理程序到链接中
open(url,data=None[,timeout]):打开给定的URL与urlopen()方法相同
error(proto,*args):处理给定协议的错误
更多Request内容...
密码验证:
#!/usr/bin/env python
#coding:utf8
from urllib.request import HTTPPasswordMgrWithDefaultRealm,HTTPBasicAuthHandler,build_opener
from urllib.error import URLError
username='username'
passowrd='password'
url='http://localhost'
p=HTTPPasswordMgrWithDefaultRealm() #构造密码管理实例
p.add_password(None,url,username,passowrd) #添加用户名和密码到实例中
auth_handler=HTTPBasicAuthHandler(p) #传递密码管理实例构建一个验证实例
opener=build_opener(auth_handler) #构建一个Openertry:
result=opener.open(url) #打开链接,完成验证,返回的结果是验证后的页面内容
html=result.read().decode('utf-8')
print(html)except URLError as e:
print(e.reason)
代理设置:
#!/usr/bin/env python
#coding:utf8from urllib.error import URLErrorfrom urllib.request import ProxyHandler,build_opener
proxy_handler=ProxyHandler({
'http':'http://127.0.0.1:8888',
'https':'http://127.0.0.1:9999'
})
opener=build_opener(proxy_handler) #构造一个Openertry:
response=opener.open('https://www.baidu.com')
print(response.read().decode('utf-8'))except URLError as e:
print(e.reason)
Cookies:
获取网站的Cookies
#!/usr/bin/env python
#coding:utf8import http.cookiejar,urllib.request
cookie=http.cookiejar.CookieJar() #实例化cookiejar对象
handler=urllib.request.HTTPCookieProcessor(cookie) #构建一个handler
opener=urllib.request.build_opener(handler) #构建Opener
response=opener.open('http://www.baidu.com') #请求print(cookie)for item in cookie:
print(item.name+"="+item.value)
Mozilla型浏览器的cookies格式,保存到文件:
#!/usr/bin/env python
#coding:utf8import http.cookiejar,urllib.request
fielname='cookies.txt'
cookie=http.cookiejar.MozillaCookieJar(filename=fielname) #创建保存cookie的实例,保存浏览器类型的Mozilla的cookie格式
#cookie=http.cookiejar.CookieJar() #实例化cookiejar对象
handler=urllib.request.HTTPCookieProcessor(cookie) #构建一个handler
opener=urllib.request.build_opener(handler) #构建Opener
response=opener.open('http://www.baidu.com') #请求
cookie.save(ignore_discard=True,ignore_expires=True)
也可以保存为libwww-perl(LWP)格式的Cookies文件
cookie=http.cookiejar.LWPCookieJar(filename=fielname)
从文件中读取cookies:
#!/usr/bin/env python
#coding:utf8import http.cookiejar,urllib.request#fielname='cookiesLWP.txt'
#cookie=http.cookiejar.MozillaCookieJar(filename=fielname) #创建保存cookie的实例,保存浏览器类型的Mozilla的cookie格式
#cookie=http.cookiejar.LWPCookieJar(filename=fielname) #LWP格式的cookies
#cookie=http.cookiejar.CookieJar() #实例化cookiejar对象
cookie=http.cookiejar.LWPCookieJar()
cookie.load('cookiesLWP.txt',ignore_discard=True,ignore_expires=True)
handler=urllib.request.HTTPCookieProcessor(cookie) #构建一个handler
opener=urllib.request.build_opener(handler) #构建Opener
response=opener.open('http://www.baidu.com') #请求print(response.read().decode('utf-8'))
4、异常处理
urllib的error模块定义了由request模块产生的异常,如果出现问题,request模块便会抛出error模块中定义的异常。
1)URLError
URLError类来自urllib库的error模块,它继承自OSError类,是error异常模块的基类,由request模块产生的异常都可以通过捕获这个类来处理
它只有一个属性reason,即返回错误的原因
#!/usr/bin/env python
#coding:utf8from urllib import request,error
try:
response=request.urlopen('https://hehe,com/index')except error.URLError as e:
print(e.reason) #如果网页不存在不会抛出异常,而是返回捕获的异常错误的原因(Not Found)
reason如超时则返回一个对象
#!/usr/bin/env python
#coding:utf8
import socketimport urllib.requestimport urllib.errortry:
response=urllib.request.urlopen('https://www.baidu.com',timeout=0.001)except urllib.error.URLError as e:
print(e.reason)
if isinstance(e.reason,socket.timeout):
print('time out')
2)HTTPError
它是URLError的子类,专门用来处理HTTP请求错误,比如认证请求失败,它有3个属性:
code:返回HTTP的状态码,如404页面不存在,500服务器错误等
reason:同父类,返回错误的原因
headers:返回请求头
更多error内容...
#!/usr/bin/env python
#coding:utf8from urllib import request,error
try:
response=request.urlopen('http://cuiqingcai.com/index.htm')except error.HTTPError as e: #先捕获子类异常
print(e.reason,e.code,e.headers,sep='
')except error.URLError as e: #再捕获父类异常
print(e.reason)else:
print('request successfully')
5、解析链接
urllib库提供了parse模块,它定义了处理URL的标准接口,如实现URL各部分的抽取,合并以及链接转换,它支持如下协议的URL处理:file,ftp,gopher,hdl,http,https,imap,mailto,mms,news,nntp,prospero,rsync,rtsp,rtspu,sftp,sip,sips,snews,svn,snv+ssh,telnet,wais
urllib.parse.urlparse(urlstring,scheme='',allow_fragments=True)
通过urlparse的API可以看到,它还可以传递3个参数
urlstring:待解析的URL,字符串
scheme:它是默认的协议,如http或者https,URL如果不带http协议,可以通过scheme来指定,如果URL中制定了http协议则URL中生效
allow_fragments:是否忽略fragment即锚点,如果设置为False,fragment部分会被忽略,反之不忽略
更多parse模块内容...
1)urlparse()
该方法可以实现URL的识别和分段,分别是scheme(协议),netloc(域名),path(路径),params(参数),query(查询条件),fragment(锚点)
#!/usr/bin/env python
#coding:utf8from urllib.parse import urlparse
result=urlparse('http://www.baidu.com/index.html;user?id=5#comment')
print(type(result),result,sep='
') #返回的是一个元祖print(result.scheme,result[0]) #可以通过属性或者索引来获取值print(result.netloc,result[1])
print(result.path,result[2])
print(result.params,result[3])
print(result.query,result[4])
print(result.fragment,result[5])
#output
#返回结果是一个parseresult类型的对象,它包含6个部分,
#分别是scheme(协议),netloc(域名),path(路径),params(参数),query(查询条件),fragment(锚点)
<class 'urllib.parse.ParseResult'>
ParseResult(scheme='http', netloc='www.baidu.com', path='/index.html', params='user', query='id=5', fragment='comment')
http http
www.baidu.com www.baidu.com/index.html /index.html
user user
id=5 id=5
comment comment
指定scheme协议,allow_fragments忽略锚点信息:
from urllib.parse import urlparse
result=urlparse('www.baidu.com/index.html;user?id=5#comment',scheme='https',allow_fragments=False)print(result)
#output
ParseResult(scheme='https', netloc='', path='www.baidu.com/index.html', params='user', query='id=5#comment', fragment='')
2)urlunparse()
与urlparse()相反,通过列表或者元祖的形式接受一个可迭代的对象,实现URL构造
#!/usr/bin/env python
#coding:utf8from urllib.parse import urlunparse
data=['http','www.baidu.com','index.html','user','a=6','comment']print(urlunparse(data)) #构造一个完整的URL
#output
http://www.baidu.com/index.html;user?a=6#comment
3)urlsplit()
与urlparse()方法类似,它会返回5个部分,把params合并到path中
#!/usr/bin/env python
#coding:utf8from urllib.parse import urlsplit
result=urlsplit('http://www.baidu.com/index.html;user?id=5#comment')print(result)
#output
SplitResult(scheme='http', netloc='www.baidu.com', path='/index.html;user', query='id=5', fragment='comment')
4)urlunsplit()
与urlunparse()类似,它也是将链接的各部分组合完整的链接的方法,传入的参数也是可迭代的对象,如列表元祖等,唯一的区别是长度必须是5个,它省略了params
#!/usr/bin/env python
#coding:utf8from urllib.parse import urlsplit,urlunsplit
data=['http','www.baidu.com','index.html','a=5','comment']
result=urlunsplit(data)print(result)
#output
http://www.baidu.com/index.html?a=5#comment
5)urljoin()
通过将基本URL(base)与另一个URL(url)组合起来构建完整URL,它会使用基本URL组件,协议(schemm)、域名(netloc)、路径(path)、来提供给URL中缺失的部分进行补充,最后返回结果
#!/usr/bin/env python
#coding:utf8from urllib.parse import urljoin
print(urljoin('http://www.baidu.com','index.html'))
print(urljoin('http://www.baidu.com','http://cdblogs.com/index.html'))
print(urljoin('http://www.baidu.com/home.html','https://cnblog.com/index.html'))
print(urljoin('http://www.baidu.com?id=3','https://cnblog.com/index.html?id=6'))
print(urljoin('http://www.baidu.com','?id=2#comment'))
print(urljoin('www.baidu.com','https://cnblog.com/index.html?id=6'))
#output
http://www.baidu.com/index.html
http://cdblogs.com/index.html
https://cnblog.com/index.html
https://cnblog.com/index.html?id=6
http://www.baidu.com?id=2#comment
https://cnblog.com/index.html?id=6
base_url提供了三项内容scheme,netloc,path,如果这3项在新的链接中不存在就给予补充,如果新的链接存在就使用新的链接部分,而base_url中的params,query和fragment是不起作用的。通过urljoin()方法可以实现链接的解析、拼接和生成
6)urlencode()
urlencode()在构造GET请求参数时很有用,它可以将字典转化为GET请求参数
#!/usr/bin/env python
#coding:utf8from urllib.parse import urlencode
params = {'username':'zs','password':'123'}
base_url='http://www.baidu.com'
url=base_url+'?'+urlencode(params) #将字典转化为get参数print(url)
#output
http://www.baidu.com?password=123&username=zs
7)parse_qs()
parse_qs()与urlencode()正好相反,它是用来反序列化的,如将GET参数转换回字典格式
#!/usr/bin/env python
#coding:utf8from urllib.parse import urlencode,parse_qs,urlsplit
params = {'username':'zs','password':'123'}
base_url='http://www.baidu.com'
url=base_url+'?'+urlencode(params) #将字典转化为get参数
query=urlsplit(url).query #获去URL的query参数条件print(parse_qs(query)) #根据获取的GET参数转换为字典格式
#output
{'username': ['zs'], 'password': ['123']}
8)parse_qsl()它将参数转换为元祖组成的列表
#!/usr/bin/env python
#coding:utf8from urllib.parse import urlencode,urlsplit,parse_qsl
params = {'username':'zs','password':'123'}
base_url='http://www.baidu.com'
url=base_url+'?'+urlencode(params) #将字典转化为get参数
query=urlsplit(url).query #获去URL的query参数条件print(parse_qsl(query)) #将转换成列表形式的元祖对
#output
[('username', 'zs'), ('password', '123')]
9)quote():该方法可以将内容转换为URL编码的格式,如参数中带有中文时,有时会导致乱码的问题,此时用这个方法将中文字符转化为URL编码
#!/usr/bin/env python
#coding:utf8from urllib.parse import quote
key='中文'
url='https://www.baidu.com/s?key='+quote(key)print(url)
#output
https://www.baidu.com/s?key=%E4%B8%AD%E6%96%87
10)unquote():与quote()相反,他用来进行URL解码
#!/usr/bin/env python
#coding:utf8from urllib.parse import quote,urlsplit,unquote
key='中文'
url='https://www.baidu.com/s?key='+quote(key)print(url)
unq=urlsplit(url).query.split('=')[1] #获取参数值
print(unquote(unq)) #解码参数
6、分析Robots协议
利用urllib的robotparser模块,我们可以实现网站Robots协议的分析
1)Robots协议
Robots协议也称为爬虫协议、机器人协议,它的全名叫做网络爬虫排除标准(Robots Exclusion Protocol),用来告诉爬虫和搜索引擎哪些网页可以抓取,哪些不可以抓取,它通常是一个robots.txt的文本文件,一般放在网站的根目录下。
当搜索爬虫访问一个站点时,它首先会检查这个站点根目录下是否存在robots.txt文件,如果存在,搜索爬虫会根据其中定义的爬去范围来爬取,如果没有找到,搜索爬虫会访问所有可直接访问的页面
我们来看下robots.txt的样例:
User-agent: *
Disallow: /
Allow: /public/
它实现了对所有搜索爬虫只允许爬取public目录的功能,将上述内容保存为robots.txt文件放在网站根目录下,和网站的入口文件(index.html)放在一起
User-agent描述了搜索爬虫的名称,将其设置为*则代表协议对任何爬虫有效,如设置为Baiduspider则代表规则对百度爬虫有效,如果有多条则对多个爬虫受到限制,但至少需要指定一条
一些常见的搜索爬虫名称:
BaiduSpider 百度爬虫 www.baidu.com
Googlebot Google爬虫 www.google.com
360Spider 360爬虫 www.so.com
YodaoBot 有道爬虫 www.youdao.com
ia_archiver Alexa爬虫 www.alexa.cn
Scooter altavista爬虫 www.altavista.com
Disallow指定了不允许抓取的目录,如上例中设置的/则代表不允许抓取所有的页面
Allow一般和Disallow一起使用,用来排除单独的某些限制,如上例中设置为/public/则表示所有页面不允许抓取,但可以抓取public目录
设置示例:
#禁止所有爬虫
User-agent: *
Disallow: /
#允许所有爬虫访问任何目录,另外把文件留空也可以
User-agent: *
Disallow:
#禁止所有爬虫访问某那些目录
User-agent: *
Disallow: /home/
Disallow: /tmp/
#只允许某一个爬虫访问
User-agent: BaiduSpider
Disallow:
User-agent: *
Disallow: /
2)robotparser
rebotparser模块用来解析robots.txt,该模块提供了一个类RobotFileParser,它可以根据某网站的robots.txt文件来判断一个抓取爬虫时都有权限来抓取这个网页
urllib.robotparser.RobotFileParser(url='')
robotparser类常用的方法:
set_url():用来设置robots.txt文件的连接,如果在创建RobotFileParser对象是传入了连接,就不需要在使用这个方法设置了
read():读取reobts.txt文件并进行分析,它不会返回任何内容,但执行那个了读取和分析操作
parse():用来解析robots.txt文件,传入的参数是robots.txt某些行的内容,并安装语法规则来分析内容
can_fetch():该方法传入两个参数,第一个是User-agent,第二个是要抓取的URL,返回的内容是该搜索引擎是否可以抓取这个url,结果为True或False
mtime():返回上次抓取和分析robots.txt的时间
modified():将当前时间设置为上次抓取和分析robots.txt的时间
#!/usr/bin/env python
#coding:utf8from urllib.robotparser import RobotFileParser
rp = RobotFileParser() #创建对象
rp.set_url('https://www.cnblogs.com/robots.txt') #设置robots.txt连接,也可以在创建对象时指定
rp.read() #读取和解析文件
print(rp.can_fetch('*','https://i.cnblogs.com/EditPosts.aspx?postid=9170312&update=1')) #坚持链接是否可以被抓取
python3 requests
官网中对requests的介绍是"HTTP for Humans"
Requests allows you to send organic, grass-fed HTTP/1.1 requests, without the need for manual labor. There’s no need to manually add query strings to your URLs, or to form-encode your POST data. Keep-alive and HTTP connection pooling are 100% automatic, thanks to urllib3.
GET请求不用&拼接参数,POST请求也毋须编码请求体
总之简单易用,是接口测试和爬虫的必备神器
2 基本使用
2-1 get/post
import requests
//get无参数
r = requests.get('https://httpbin.org/get')
//get有参数,使用params
data = {'key': 'value'}
r = requests.get('https://httpbin.org/get', params=data)
//post有参数,使用data
r = requests.post('https://httpbin.org/post', data = {'key':'value'})
2-2 自定义请求头
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.62 Safari/537.36'}
r = requests.get('https://httpbin.org/get', headers=headers)
2-3 响应内容
• r.content 响应内容的字节码,一般处理二进制文件
• r.text 自动选择适当的编码,对r.content解码
• r.json() 解析json格式的数据,如果无法解析,则抛出异常
3 API
无论是get/post/delete/put/patch/head/options,都是调用request方法
参数如下:
• url 请求的URL地址
• params GET请求参数
• data POST请求参数
• json 同样是POST请求参数,要求服务端接收json格式的数据
• headers 请求头字典
• cookies cookies信息(字典或CookieJar)
• files 上传文件
• auth HTTP鉴权信息
• timeout 等待响应时间,单位秒
• allow_redirects 是否允许重定向
• proxies 代理信息
• verify 是否校验证书
• stream 如果为False,则响应内容将直接全部下载
• cert 客户端证书地址
4 Session对象
Session可以持久化请求过程中的参数,以及cookie
尤其是需要登录的网页,使用session可以避免每次的登录操作
s = requests.Session()
s.cookies = requests.cookies.cookiejar_from_dict({'key': 'value'})
r = s.get('https://httpbin.org/cookies')
print(r.text)
===========================
{
"cookies": {
"key": "value"
}
}
另外session还可提供默认值
s = requests.Session()
s.headers.update({'h1':'val1', 'h2':'val2'})
r = s.get('https://httpbin.org/headers', headers={'h2': 'val2_modify'})
print(r.text)
============================
"H1": "val1",
"H2": "val2_modify",
5 Response对象
字段
• cookies 返回CookieJar对象
• encoding 报文的编码
• headers 响应头
• history 重定向的历史记录
• status_code 响应状态码,如200
• elaspsed 发送请求到接收响应耗时
• text 解码后的报文主体
• content 字节码,可能在raw的基础上解压
方法
• json() 解析json格式的响应
• iter_content() 需配置stream=True,指定chunk_size大小
• iter_lines() 需配置stream=True,每次返回一行
• raise_for_status() 400-500之间将抛出异常
• close()
6 Prepared Requests
一般情况下,会一次性包装好请求头,请求参数,cookies,鉴权等;但如果通过某些条件判断,可以局部组装requests
s = requests.Session()
req = requests.Request('GET', url='https://httpbin.org/get')
prep = s.prepare_request(req)
headers = {
'User-Agent': 'Chrome/67.0.3396.62'}
prep.prepare(
method='POST',
url='https://httpbin.org/post',
headers=headers,
data={'key': 'value'})
r = s.send(prep)print(r.text)==================={
"args": {},
"data": "",
"files": {},
"form": {
"key": "value"
},
"headers": {
"Accept-Encoding": "identity",
"Connection": "close",
"Content-Length": "9",
"Content-Type": "application/x-www-form-urlencoded",
"Host": "httpbin.org",
"User-Agent": "Chrome/67.0.3396.62"
},
"json": null,
"origin": "xx.xx.xx.xx",
"url": "https://httpbin.org/post"}
python3解析库lxml
lxml是python的一个解析库,支持HTML和XML的解析,支持XPath解析方式,而且解析效率非常高
XPath,全称XML Path Language,即XML路径语言,它是一门在XML文档中查找信息的语言,它最初是用来搜寻XML文档的,但是它同样适用于HTML文档的搜索
XPath的选择功能十分强大,它提供了非常简明的路径选择表达式,另外,它还提供了超过100个内建函数,用于字符串、数值、时间的匹配以及节点、序列的处理等,几乎所有我们想要定位的节点,都可以用XPath来选择
XPath于1999年11月16日成为W3C标准,它被设计为供XSLT、XPointer以及其他XML解析软件使用,更多的文档可以访问其官方网站:https://www.w3.org/TR/xpath/
1、python库lxml的安装
windows系统下的安装:
#pip安装
pip3 install lxml
#wheel安装
#下载对应系统版本的wheel文件:http://www.lfd.uci.edu/~gohlke/pythonlibs/#lxml
pip3 install lxml-4.2.1-cp36-cp36m-win_amd64.whl
linux下安装:
yum install -y epel-release libxslt-devel libxml2-devel openssl-devel
pip3 install lxml
验证安装:
$python3
>>>import lxml
2、XPath常用规则
表达式 描述
nodename 选取此节点的所有子节点
/ 从当前节点选取直接子节点
// 从当前节点选取子孙节点
. 选取当前节点
.. 选取当前节点的父节点
@ 选取属性
* 通配符,选择所有元素节点与元素名
@* 选取所有属性
[@attrib] 选取具有给定属性的所有元素
[@attrib='value'] 选取给定属性具有给定值的所有元素
[tag] 选取所有具有指定元素的直接子节点
[tag='text'] 选取所有具有指定元素并且文本内容是text节点
(1)读取文本解析节点
from lxml import etree
text='''
<div>
<ul>
<li class="item-0"><a href="link1.html">第一个</a></li>
<li class="item-1"><a href="link2.html">second item</a></li>
<li class="item-0"><a href="link5.html">a属性</a>
</ul>
</div>
'''
html=etree.HTML(text) #初始化生成一个XPath解析对象
result=etree.tostring(html,encoding='utf-8') #解析对象输出代码
print(type(html))
print(type(result))
print(result.decode('utf-8'))
#etree会修复HTML文本节点
<class 'lxml.etree._Element'>
<class 'bytes'>
<html><body><div>
<ul>
<li class="item-0"><a href="link1.html">第一个</a></li>
<li class="item-1"><a href="link2.html">second item</a></li>
<li class="item-0"><a href="link5.html">a属性</a>
</li></ul>
</div>
</body></html>
(2)读取HTML文件进行解析
from lxml import etree
html=etree.parse('test.html',etree.HTMLParser()) #指定解析器HTMLParser会根据文件修复HTML文件中缺失的如声明信息
result=etree.tostring(html) #解析成字节
#result=etree.tostringlist(html) #解析成列表
print(type(html))
print(type(result))
print(result)
#
<class 'lxml.etree._ElementTree'>
<class 'bytes'>
b'<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
<html><body><div>
<ul>
<li class="item-0"><a href="link1.html">first item</a></li>
<li class="item-1"><a href="link2.html">second item</a></li>
<li class="item-inactive"><a href="link3.html">third item</a></li>
<li class="item-1"><a href="link4.html">fourth item</a></li>
<li class="item-0"><a href="link5.html">fifth item</a>
</li></ul>
</div>
</body></html>'
(3)获取所有节点
from lxml import etree
html=etree.parse('test',etree.HTMLParser())
result=html.xpath('//*') #//代表获取子孙节点,*代表获取所有
print(type(html))
print(type(result))
print(result)
#
<class 'lxml.etree._ElementTree'>
<class 'list'>
[<Element html at 0x754b210048>, <Element body at 0x754b210108>, <Element div at 0x754b210148>, <Element ul at 0x754b210188>, <Element li at 0x754b2101c8>, <Element a at 0x754b210248>, <Element li at 0x754b210288>, <Element a at 0x754b2102c8>, <Element li at 0x754b210308>, <Element a at 0x754b210208>, <Element li at 0x754b210348>, <Element a at 0x754b210388>, <Element li at 0x754b2103c8>, <Element a at 0x754b210408>]
如要获取li节点,可以使用//后面加上节点名称,然后调用xpath()方法
html.xpath('//li') #获取所有子孙节点的li节点
(4)获取子节点
通过/或者//即可查找元素的子节点或者子孙节点,如果想选择li节点的所有直接a节点,可以这样使用
result=html.xpath('//li/a') #通过追加/a选择所有li节点的所有直接a节点,因为//li用于选中所有li节点,/a用于选中li节点的所有直接子节点a
(5)获取父节点
我们知道通过连续的/或者//可以查找子节点或子孙节点,那么要查找父节点可以使用..来实现也可以使用parent::来获取父节点
from lxml import etree
from lxml.etree import HTMLParser
text='''
<div>
<ul>
<li class="item-0"><a href="link1.html">第一个</a></li>
<li class="item-1"><a href="link2.html">second item</a></li>
</ul>
</div>
'''
html=etree.HTML(text,etree.HTMLParser())
result=html.xpath('//a[@href="link2.html"]/../@class')
result1=html.xpath('//a[@href="link2.html"]/parent::*/@class')
print(result)
print(result1)
#
['item-1']
['item-1']
(6)属性匹配
在选取的时候,我们还可以用@符号进行属性过滤。比如,这里如果要选取class为item-1的li节点,可以这样实现:
from lxml import etree
from lxml.etree import HTMLParser
text='''
<div>
<ul>
<li class="item-0"><a href="link1.html">第一个</a></li>
<li class="item-1"><a href="link2.html">second item</a></li>
</ul>
</div>
'''
html=etree.HTML(text,etree.HTMLParser())
result=html.xpath('//li[@class="item-1"]')
print(result)
(7)文本获取
我们用XPath中的text()方法获取节点中的文本
from lxml import etree
text='''
<div>
<ul>
<li class="item-0"><a href="link1.html">第一个</a></li>
<li class="item-1"><a href="link2.html">second item</a></li>
</ul>
</div>
'''
html=etree.HTML(text,etree.HTMLParser())
result=html.xpath('//li[@class="item-1"]/a/text()') #获取a节点下的内容
result1=html.xpath('//li[@class="item-1"]//text()') #获取li下所有子孙节点的内容
print(result)
print(result1)
(8)属性获取
使用@符号即可获取节点的属性,如下:获取所有li节点下所有a节点的href属性
result=html.xpath('//li/a/@href') #获取a的href属性
result=html.xpath('//li//@href') #获取所有li子孙节点的href属性
回到顶部
(9)属性多值匹配
如果某个属性的值有多个时,我们可以使用contains()函数来获取
from lxml import etree
text1='''
<div>
<ul>
<li class="aaa item-0"><a href="link1.html">第一个</a></li>
<li class="bbb item-1"><a href="link2.html">second item</a></li>
</ul>
</div>
'''
html=etree.HTML(text1,etree.HTMLParser())
result=html.xpath('//li[@class="aaa"]/a/text()')
result1=html.xpath('//li[contains(@class,"aaa")]/a/text()')
print(result)
print(result1)
#通过第一种方法没有取到值,通过contains()就能精确匹配到节点了
[]
['第一个']
(10)多属性匹配
另外我们还可能遇到一种情况,那就是根据多个属性确定一个节点,这时就需要同时匹配多个属性,此时可用运用and运算符来连接使用:
from lxml import etree
text1='''
<div>
<ul>
<li class="aaa" name="item"><a href="link1.html">第一个</a></li>
<li class="aaa" name="fore"><a href="link2.html">second item</a></li>
</ul>
</div>
'''
html=etree.HTML(text1,etree.HTMLParser())
result=html.xpath('//li[@class="aaa" and @name="fore"]/a/text()')
result1=html.xpath('//li[contains(@class,"aaa") and @name="fore"]/a/text()')
print(result)
print(result1)
#
['second item']
['second item']
(11)XPath中的运算符
运算符 描述 实例 返回值
or 或 age=19 or age=20 如果age等于19或者等于20则返回true反正返回false
and 与 age>19 and age<21 如果age等于20则返回true,否则返回false
mod 取余 5 mod 2 1
| 取两个节点的集合 //book | //cd 返回所有拥有book和cd元素的节点集合
+ 加 6+4 10
- 减 6-4 2
* 乘 6*4 24
div 除法 8 div 4 2
= 等于 age=19 true
!= 不等于 age!=19 true
< 小于 age<19 true
<= 小于或等于 age<=19 true
> 大于 age>19 true
>= 大于或等于 age>=19 true
(12)按序选择
有时候,我们在选择的时候某些属性可能同时匹配多个节点,但我们只想要其中的某个节点,如第二个节点或者最后一个节点,这时可以利用中括号引入索引的方法获取特定次序的节点:
from lxml import etree
text1='''
<div>
<ul>
<li class="aaa" name="item"><a href="link1.html">第一个</a></li>
<li class="aaa" name="item"><a href="link1.html">第二个</a></li>
<li class="aaa" name="item"><a href="link1.html">第三个</a></li>
<li class="aaa" name="item"><a href="link1.html">第四个</a></li>
</ul>
</div>
'''
html=etree.HTML(text1,etree.HTMLParser())
result=html.xpath('//li[contains(@class,"aaa")]/a/text()') #获取所有li节点下a节点的内容
result1=html.xpath('//li[1][contains(@class,"aaa")]/a/text()') #获取第一个
result2=html.xpath('//li[last()][contains(@class,"aaa")]/a/text()') #获取最后一个
result3=html.xpath('//li[position()>2 and position()<4][contains(@class,"aaa")]/a/text()') #获取第一个
result4=html.xpath('//li[last()-2][contains(@class,"aaa")]/a/text()') #获取倒数第三个
print(result)
print(result1)
print(result2)
print(result3)
print(result4)
#
['第一个', '第二个', '第三个', '第四个']
['第一个']
['第四个']
['第三个']
['第二个']
这里使用了last()、position()函数,在XPath中,提供了100多个函数,包括存取、数值、字符串、逻辑、节点、序列等处理功能
(13)节点轴选择
XPath提供了很多节点选择方法,包括获取子元素、兄弟元素、父元素、祖先元素等,示例如下:
from lxml import etree
text1='''
<div>
<ul>
<li class="aaa" name="item"><a href="link1.html">第一个</a></li>
<li class="aaa" name="item"><a href="link1.html">第二个</a></li>
<li class="aaa" name="item"><a href="link1.html">第三个</a></li>
<li class="aaa" name="item"><a href="link1.html">第四个</a></li>
</ul>
</div>
'''
html=etree.HTML(text1,etree.HTMLParser())
result=html.xpath('//li[1]/ancestor::*') #获取所有祖先节点
result1=html.xpath('//li[1]/ancestor::div') #获取div祖先节点
result2=html.xpath('//li[1]/attribute::*') #获取所有属性值
result3=html.xpath('//li[1]/child::*') #获取所有直接子节点
result4=html.xpath('//li[1]/descendant::a') #获取所有子孙节点的a节点
result5=html.xpath('//li[1]/following::*') #获取当前子节之后的所有节点
result6=html.xpath('//li[1]/following-sibling::*') #获取当前节点的所有同级节点
#
[<Element html at 0x3ca6b960c8>, <Element body at 0x3ca6b96088>, <Element div at 0x3ca6b96188>, <Element ul at 0x3ca6b961c8>]
[<Element div at 0x3ca6b96188>]
['aaa', 'item']
[<Element a at 0x3ca6b96248>]
[<Element a at 0x3ca6b96248>]
[<Element li at 0x3ca6b96308>, <Element a at 0x3ca6b96348>, <Element li at 0x3ca6b96388>, <Element a at 0x3ca6b963c8>, <Element li at 0x3ca6b96408>, <Element a at 0x3ca6b96488>]
[<Element li at 0x3ca6b96308>, <Element li at 0x3ca6b96388>, <Element li at 0x3ca6b96408>]
(14)案例应用:抓取TIOBE指数前20名排行开发语言
#!/usr/bin/env python
#coding:utf-8
import requests
from requests.exceptions import RequestException
from lxml import etree
from lxml.etree import ParseError
import json
def one_to_page(html):
headers={
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.62 Safari/537.36'
}
try:
response=requests.get(html,headers=headers)
body=response.text #获取网页内容
except RequestException as e:
print('request is error!',e)
try:
html=etree.HTML(body,etree.HTMLParser()) #解析HTML文本内容
result=html.xpath('//table[contains(@class,"table-top20")]/tbody/tr//text()') #获取列表数据
pos = 0
for i in range(20):
if i == 0:
yield result[i:5]
else:
yield result[pos:pos+5] #返回排名生成器数据
pos+=5
except ParseError as e:
print(e.position)
def write_file(data): #将数据重新组合成字典写入文件并输出
for i in data:
sul={
'2018年6月排行':i[0],
'2017年6排行':i[1],
'开发语言':i[2],
'评级':i[3],
'变化率':i[4]
}
with open('test.txt','a',encoding='utf-8') as f:
f.write(json.dumps(sul,ensure_ascii=False) + '
') #必须格式化数据
f.close()
print(sul)
return None
def main():
url='https://www.tiobe.com/tiobe-index/'
data=one_to_page(url)
revaule=write_file(data)
if revaule == None:
print('ok')
if __name__ == '__main__':
main()
#
{'2018年6月排行': '1', '2017年6排行': '1', '开发语言': 'Java', '评级': '15.368%', '变化率': '+0.88%'}
{'2018年6月排行': '2', '2017年6排行': '2', '开发语言': 'C', '评级': '14.936%', '变化率': '+8.09%'}
{'2018年6月排行': '3', '2017年6排行': '3', '开发语言': 'C++', '评级': '8.337%', '变化率': '+2.61%'}
{'2018年6月排行': '4', '2017年6排行': '4', '开发语言': 'Python', '评级': '5.761%', '变化率': '+1.43%'}
{'2018年6月排行': '5', '2017年6排行': '5', '开发语言': 'C#', '评级': '4.314%', '变化率': '+0.78%'}
{'2018年6月排行': '6', '2017年6排行': '6', '开发语言': 'Visual Basic .NET', '评级': '3.762%', '变化率': '+0.65%'}
{'2018年6月排行': '7', '2017年6排行': '8', '开发语言': 'PHP', '评级': '2.881%', '变化率': '+0.11%'}
{'2018年6月排行': '8', '2017年6排行': '7', '开发语言': 'JavaScript', '评级': '2.495%', '变化率': '-0.53%'}
{'2018年6月排行': '9', '2017年6排行': '-', '开发语言': 'SQL', '评级': '2.339%', '变化率': '+2.34%'}
{'2018年6月排行': '10', '2017年6排行': '14', '开发语言': 'R', '评级': '1.452%', '变化率': '-0.70%'}
{'2018年6月排行': '11', '2017年6排行': '11', '开发语言': 'Ruby', '评级': '1.253%', '变化率': '-0.97%'}
{'2018年6月排行': '12', '2017年6排行': '18', '开发语言': 'Objective-C', '评级': '1.181%', '变化率': '-0.78%'}
{'2018年6月排行': '13', '2017年6排行': '16', '开发语言': 'Visual Basic', '评级': '1.154%', '变化率': '-0.86%'}
{'2018年6月排行': '14', '2017年6排行': '9', '开发语言': 'Perl', '评级': '1.147%', '变化率': '-1.16%'}
{'2018年6月排行': '15', '2017年6排行': '12', '开发语言': 'Swift', '评级': '1.145%', '变化率': '-1.06%'}
{'2018年6月排行': '16', '2017年6排行': '10', '开发语言': 'Assembly language', '评级': '0.915%', '变化率': '-1.34%'}
{'2018年6月排行': '17', '2017年6排行': '17', '开发语言': 'MATLAB', '评级': '0.894%', '变化率': '-1.10%'}
{'2018年6月排行': '18', '2017年6排行': '15', '开发语言': 'Go', '评级': '0.879%', '变化率': '-1.17%'}
{'2018年6月排行': '19', '2017年6排行': '13', '开发语言': 'Delphi/Object Pascal', '评级': '0.875%', '变化率': '-1.28%'}
{'2018年6月排行': '20', '2017年6排行': '20', '开发语言': 'PL/SQL', '评级': '0.848%', '变化率': '-0.72%'}
1. 创建 BeautifulSoup 对象
首先导入库 bs4 lxml requests
1. #encoding:UTF-8
2. from bs4 import BeautifulSoup
3. import lxml
4. import requests
使用官方字符串来演示:
1. html = """
2. <html><head><title>The Dormouse's story</title></head>
3. <body>
4. <p class="title" name="dromouse"><b>The Dormouse's story</b></p>
5. <p class="story">Once upon a time there were three little sisters; and their names were
6. <a href="http://example.com/elsie" class="sister" id="link1"><!-- Elsie --></a>,
7. <a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and
8. <a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>;
9. and they lived at the bottom of a well.</p>
10. <p class="story">...</p>
11. """
创建 beautifulsoup 对象:
soup = BeautifulSoup(html,'lxml') #创建 beautifulsoup 对象
还可以用本地 HTML 文件来创建对象:
soup1 = BeautifulSoup(open('index.html')) #用本地 HTML 文件来创建对象
打印一下 soup 对象的内容,格式化输出:
print soup.prettify() #打印 soup 对象的内容,格式化输出
输出结果,格式化打印出了它的内容,这个函数经常用到。
2. 四种对象
Beautiful Soup将复杂HTML文档转换成一个复杂的树形结构,每个节点都是Python对象,所有对象可以归纳为4种:
• Tag
• NavigableString
• BeautifulSoup
• Comment
(1)Tag
Tag就是 HTML 中的一个个标签,例如:
<title>The Dormouse's story</title>
用 BeautifulSoup 可以很方便地获取 Tags:
1. print soup.title
2. print soup.head
3. print soup.a
4. print soup.p
5. print type(soup.a)
这种方式查找的是在所有内容中的第一个符合要求的标签。
对于 Tag,它有两个重要的属性,name 和 attrs :
1. print soup.name
2. print soup.a.name
3. print soup.attrs
4. print soup.p.attrs #在这里,我们把 p 标签的所有属性打印输出了出来,得到的类型是一个字典。
5. print soup.p['class'] #单独获取某个属性
6. print soup.p.get('class') ##单独获取某个属性 跟上面一样的
可以对这些属性和内容等等进行修改:
soup.p['class']="newClass"
可以对这个属性进行删除:
del soup.p['class']
(2)NavigableString
得到了标签的内容用 .string 即可获取标签内部的文字,例如:
print soup.p.string
来检查一下它的类型:
1. print type(soup.p.string)
2. #<class 'bs4.element.NavigableString'>
可以看到它的类型是一个 NavigableString,翻译过来叫 可以遍历的字符串。
(3)BeautifulSoup
BeautifulSoup 对象表示的是一个文档的全部内容.大部分时候,可以把它当作 Tag 对象,是一个特殊的 Tag,我们可以分别获取它的类型,名称:
1. print type(soup.name)
2. #<type 'unicode'>
3. print soup.name
4. # [document]
5. print soup.attrs
6. #{} 空字典
(4)Comment
Comment 对象是一个特殊类型的 NavigableString 对象,其实输出的内容仍然不包括注释符号。我们找一个带注释的标签:
1. print soup.a
2. print soup.a.string
3. print type(soup.a.string)
运行结果如下:
1.
2. <a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a>
3. Elsie
4. <class 'bs4.element.Comment'>
a 标签里的内容实际上是注释,但是如果我们利用 .string 来输出它的内容,我们发现它已经把注释符号去掉了,所以这可能会给我们带来不必要的麻烦。
我们打印输出下它的类型,发现它是一个 Comment 类型,所以,我们在使用前最好做一下判断,判断代码如下:
1. if type(soup.a.string)==bs4.element.Comment:
2. print soup.a.string
上面的代码中,我们判断了它的类型,是否为 Comment 类型。
3. 遍历文档树
(1)直接子节点
tag 的 .content 属性可以将tag的子节点以列表的方式输出:
print soup.head.contents
运行结果:
[<title>The Dormouse's story</title>]
输出方式为列表,我们可以用列表索引来获取它的某一个元素:
print soup.head.contents[0]
.children
它返回的不是一个 list,不过我们可以通过遍历获取所有子节点。我们打印输出 .children 看一下,可以发现它是一个 list 生成器对象:
print soup.head.children
运行结果:
<listiterator object at 0x7f71457f5710>
遍历一下获得里面的内容:
1. for item in soup.body.children:
2. print item
(2)所有子孙节点
.contents 和 .children 属性仅包含tag的直接子节点,.descendants 属性可以对所有tag的子孙节点进行递归循环,和 children类似,要获取其中的内容,我们需要对其进行遍历:
1. for item in soup.descendants:
2. print item
查看运行结果,可以发现,所有的节点都被打印出来了:
1. <html><head><title>The Dormouse's story</title></head>
2. <body>
3. <p class="title" name="dromouse"><b>The Dormouse's story</b></p>
4. <p class="story">Once upon a time there were three little sisters; and their names were
5. <a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a>,
(3)节点内容
如果一个标签里面没有标签了,那么 .string 就会返回标签里面的内容。如果标签里面只有唯一的一个标签了,那么 .string 也会返回最里面的内容:
print soup.head.string
运行结果:
The Dormouse's story
第二种情况:
print soup.title.string
运行结果:
The Dormouse's story
如果tag包含了多个子节点,tag就无法确定,string 方法应该调用哪个子节点的内容, .string 的输出结果是 None:
print soup.html.string
运行结果:
None
(4)多个内容
.strings 获取多个内容,不过需要遍历获取,比如下面的例子:
1. for string in soup.strings:
2. print(repr(string))
输出的字符串中可能包含了很多空格或空行,使用 .stripped_strings 可以去除多余空白内容:
1. for string in soup.stripped_strings:
2. print(repr(string))
(5)父节点
1. p = soup.p
2. print p.parent.name
运行结果:
body
1. content = soup.head.title.string
2. print content.parent.name
运行结果:
title
(6)全部父节点
通过元素的 .parents 属性可以递归得到元素的所有父辈节点:
1. content = soup.head.title.string
2. for parent in content.parents:
3. print parent.name
(7)兄弟节点
兄弟节点可以理解为和本节点处在统一级的节点,.next_sibling 属性获取了该节点的下一个兄弟节点,.previous_sibling 属性获取了该节点的上一个兄弟节点,如果节点不存在,则返回 None
注意:实际文档中的tag的 .next_sibling 和 .previous_sibling 属性通常是字符串或空白,因为空白或者换行也可以被视作一个节点,所以得到的结果可能是空白或者换行。
1. print soup.p.next_sibling
2. # 实际该处为空白
3. print soup.p.prev_sibling
4. #None 没有前一个兄弟节点,返回 None
5. print soup.p.next_sibling.next_sibling
(8)全部兄弟节点
通过 .next_siblings 和 .previous_siblings 属性可以对当前节点的兄弟节点迭代输出:
1. for sibling in soup.a.next_siblings:
2. print(repr(sibling))
(9)前后节点
与 .next_sibling .previous_sibling 不同,它并不是针对于兄弟节点,而是在所有节点,不分层次
比如 head 节点为:
<head><title>The Dormouse's story</title></head>
那么它的下一个节点便是 title,它是不分层次关系的。
1. print soup.head.next_element
2. #<title>The Dormouse's story</title>
(10)所有前后节点
通过 .next_elements 和 .previous_elements 的迭代器就可以向前或向后访问文档的解析内容:
1. for element in last_a_tag.next_elements:
2. print(repr(element))
4. 搜索文档树
(1)find_all( name , attrs , recursive , text , **kwargs )
find_all() 方法搜索当前tag的所有tag子节点,并判断是否符合过滤器的条件
• name 参数
A.传字符串
name 参数可以查找所有名字为 name 的tag,字符串对象会被自动忽略掉
最简单的过滤器是字符串.在搜索方法中传入一个字符串参数,Beautiful Soup会查找与字符串完整匹配的内容,下面的例子用于查找文档中所有的<b>标签:
1. soup.find_all('b')
2. # [<b>The Dormouse's story</b>]
B.传正则表达式
如果传入正则表达式作为参数,Beautiful Soup会通过正则表达式的 match() 来匹配内容.下面例子中找出所有以b开头的标签,这表示<body>和<b>标签都应该被找到
1. import re
2. for tag in soup.find_all(re.compile("^b")):
3. print(tag.name)
4. # body
5. # b
C.传列表
如果传入列表参数,Beautiful Soup会将与列表中任一元素匹配的内容返回.下面代码找到文档中所有<a>标签和<b>标签:
1. soup.find_all(["a", "b"])
2. # [<b>The Dormouse's story</b>,
3. # <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
4. # <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
5. # <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
D.传 True
True 可以匹配任何值,下面代码查找到所有的tag,但是不会返回字符串节点:
1. for tag in soup.find_all(True):
2. print(tag.name)
3. # html
4. # head
5. # title
6. # body
7. # p
8. # b
9. # p
10. # a
11. # a
E.传方法
如果没有合适过滤器,那么还可以定义一个方法,方法只接受一个元素参数 ,如果这个方法返回 True 表示当前元素匹配并且被找到,如果不是则反回 False。下面方法校验了当前元素,如果包含 class 属性却不包含 id 属性,那么将返回 True:
1. def has_class_but_no_id(tag):
2. return tag.has_attr('class') and not tag.has_attr('id')
将这个方法作为参数传入 find_all() 方法,将得到所有<p>标签:
1. soup.find_all(has_class_but_no_id)
2. # [<p class="title"><b>The Dormouse's story</b></p>,
3. # <p class="story">Once upon a time there were...</p>,
4. # <p class="story">...</p>]
2)keyword 参数
如果一个指定名字的参数不是搜索内置的参数名,搜索时会把该参数当作指定名字tag的属性来搜索,如果包含一个名字为 id 的参数,Beautiful Soup会搜索每个tag的”id”属性:
1. 2
2. soup.find_all(id='link2')
3. # [<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]
如果传入 href 参数,Beautiful Soup会搜索每个tag的”href”属性:
1. soup.find_all(href=re.compile("elsie"))
2. # [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>]
使用多个指定名字的参数可以同时过滤tag的多个属性:
1. soup.find_all(href=re.compile("elsie"), id='link1')
2. # [<a class="sister" href="http://example.com/elsie" id="link1">three</a>]
在这里我们想用 class 过滤,不过 class 是 python 的关键词,这怎么办?加个下划线就可以:
1. soup.find_all("a", class_="sister")
2. # [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
3. # <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
4. # <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
有些tag属性在搜索不能使用,比如HTML5中的 data-* 属性:
1. data_soup = BeautifulSoup('<div data-foo="value">foo!</div>')
2. data_soup.find_all(data-foo="value")
3. # SyntaxError: keyword can't be an expression
可以通过 find_all() 方法的 attrs 参数定义一个字典参数来搜索包含特殊属性的tag:
1. data_soup.find_all(attrs={"data-foo": "value"})
2. # [<div data-foo="value">foo!</div>]
3)text 参数
通过 text 参数可以搜搜文档中的字符串内容.与 name 参数的可选值一样, text 参数接受 字符串 , 正则表达式 , 列表, True:
1. soup.find_all(text="Elsie")
2. # [u'Elsie']
3.
4. soup.find_all(text=["Tillie", "Elsie", "Lacie"])
5. # [u'Elsie', u'Lacie', u'Tillie']
6.
7. soup.find_all(text=re.compile("Dormouse"))
8. [u"The Dormouse's story", u"The Dormouse's story"]
4)limit 参数
find_all() 方法返回全部的搜索结构,如果文档树很大那么搜索会很慢.如果我们不需要全部结果,可以使用 limit 参数限制返回结果的数量.效果与SQL中的limit关键字类似,当搜索到的结果数量达到 limit 的限制时,就停止搜索返回结果.
文档树中有3个tag符合搜索条件,但结果只返回了2个,因为我们限制了返回数量:
1. soup.find_all("a", limit=2)
2. # [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
3. # <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]
5)recursive 参数
调用tag的 find_all() 方法时,Beautiful Soup会检索当前tag的所有子孙节点,如果只想搜索tag的直接子节点,可以使用参数 recursive=False:
1. soup.html.find_all("title")
2. # [<title>The Dormouse's story</title>]
3.
4. soup.html.find_all("title", recursive=False)
(2)find( name , attrs , recursive , text , **kwargs )
它与 find_all() 方法唯一的区别是 find_all() 方法的返回结果是值包含一个元素的列表,而 find() 方法直接返回结果
(3)find_parents() find_parent()
find_all() 和 find() 只搜索当前节点的所有子节点,孙子节点等. find_parents() 和 find_parent() 用来搜索当前节点的父辈节点,搜索方法与普通tag的搜索方法相同,搜索文档搜索文档包含的内容
(4)find_next_siblings() find_next_sibling()
这2个方法通过 .next_siblings 属性对当 tag 的所有后面解析的兄弟 tag 节点进行迭代, find_next_siblings() 方法返回所有符合条件的后面的兄弟节点,find_next_sibling() 只返回符合条件的后面的第一个tag节点
(5)find_previous_siblings() find_previous_sibling()
这2个方法通过 .previous_siblings 属性对当前 tag 的前面解析的兄弟 tag 节点进行迭代, find_previous_siblings()方法返回所有符合条件的前面的兄弟节点, find_previous_sibling() 方法返回第一个符合条件的前面的兄弟节点
(6)find_all_next() find_next()
这2个方法通过 .next_elements 属性对当前 tag 的之后的 tag 和字符串进行迭代, find_all_next() 方法返回所有符合条件的节点, find_next() 方法返回第一个符合条件的节点
(7)find_all_previous() 和 find_previous()
这2个方法通过 .previous_elements 属性对当前节点前面的 tag 和字符串进行迭代, find_all_previous() 方法返回所有符合条件的节点, find_previous()方法返回第一个符合条件的节点
注:以上(2)(3)(4)(5)(6)(7)方法参数用法与 find_all() 完全相同,原理均类似。
5. CSS选择器
我们在写 CSS 时,标签名不加任何修饰,类名前加点,id名前加 #,在这里我们也可以利用类似的方法来筛选元素,用到的方法是 soup.select(),返回类型是 list
(1)通过标签名查找
1. print soup.select('title')
2. #[<title>The Dormouse's story</title>]
1. print soup.select('a')
2. #[<a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a>, <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>, <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
(2)通过类名查找
1. print soup.select('.sister')
2. #[<a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a>, <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>, <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
(3)通过 id 名查找
1. print soup.select('#link1')
2. #[<a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a>]
(4)组合查找
组合查找即和写 class 文件时,标签名与类名、id名进行的组合原理是一样的,例如查找 p 标签中,id 等于 link1的内容,二者需要用空格分开:
1. print soup.select('p #link1')
2. #[<a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a>]
直接子标签查找:
1. print soup.select("head > title")
2. #[<title>The Dormouse's story</title>]
(5)属性查找
查找时还可以加入属性元素,属性需要用中括号括起来,注意属性和标签属于同一节点,所以中间不能加空格,否则会无法匹配到:
1. print soup.select('a[class="sister"]')
2. #[<a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a>, <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>, <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
1. print soup.select('a[href="http://example.com/elsie"]')
2. #[<a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a>]
属性也可以与上述查找方式组合,不在同一节点的空格隔开,同一节点的不加空格:
1. print soup.select('p a[href="http://example.com/elsie"]')
2. #[<a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a>]
以上的 select 方法返回的结果都是列表形式,可以遍历形式输出,然后用 get_text() 方法来获取它的内容:
1. soup = BeautifulSoup(html, 'lxml')
2. print type(soup.select('title'))
3. print soup.select('title')[0].get_text()
4.
5. for title in soup.select('title'):
6. print title.get_text()
以上文字,是别的老师整理的.