• 爬虫 urllib,requests,lxml,bs4 小结


    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>&#13;
        <ul>&#13;
             <li class="item-0"><a href="link1.html">first item</a></li>&#13;
             
    <li class="item-1"><a href="link2.html">second item</a></li>&#13;
            
     <li class="item-inactive"><a href="link3.html">third item</a></li>&#13;
             
    <li class="item-1"><a href="link4.html">fourth item</a></li>&#13;
             
    <li class="item-0"><a href="link5.html">fifth item</a>&#13;
    </li></ul>&#13;
     </div>&#13;
    </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()

    以上文字,是别的老师整理的.

     

  • 相关阅读:
    自定义CopyOnWriteHashMap
    NIO中Buffer缓冲区的实现
    TOMCAT原理详解及请求过程
    XSS的原理分析与解剖
    mysql分页查询优化
    java如何正确停止一个线程
    Centos搭建ElasticSearch
    redis集群原理
    Idea-每次修改JS文件都需要重启Idea才能生效解决方法
    java 加密 解密 Illegal key size
  • 原文地址:https://www.cnblogs.com/hany-postq473111315/p/13819441.html
Copyright © 2020-2023  润新知