整合營銷服務(wù)商

          電腦端+手機(jī)端+微信端=數(shù)據(jù)同步管理

          免費(fèi)咨詢熱線:

          「Python自學(xué)筆記」新手必備!Python爬蟲requests_html模塊

          • 基本使用
            • 發(fā)送請求
            • 解析響應(yīng)獲
          • 獲取需要的內(nèi)容
            • 快速獲取鏈接
            • 獲取元素
          • 高級功能
            • JS渲染
            • 自動翻頁(不太好用)
          • 異步
            • 異步渲染JS
            • 異步發(fā)送請求

          初識requests_html模塊

          感覺只要學(xué)過Python爬蟲的同學(xué)應(yīng)該都知道requests這個(gè)庫吧,它在我們的Python爬蟲任務(wù)中應(yīng)該是最常用的一個(gè)庫了!今天跟大家分享的這個(gè)模塊requests_html,他的作者和前者是同一人!這是一個(gè)解析HTML的庫,用起來和requests一樣方便,下面就來介紹一下它!

          • 參考視頻

          使用requests_html

          安裝

          • 依然是那個(gè)命令 pip3 install -i https://pypi.doubanio.com/simple requests_html
          • 注意:由于requests_html模塊中使用了異步asynico模塊,所以官方聲明,需要在python3.6以上版本才能正常使用!

          基本使用

          發(fā)送請求

          • requests_html發(fā)送請求獲取頁面需要先實(shí)例化一個(gè)HTMLSession對象,然后使用get/post...方法獲取響應(yīng),如下列代碼
          #!/usr/bin/env python3
          # coding     : utf-8
          # Author     : xiao qiang
          # 微信公眾號   : xiaoqiangclub
          # Software   : PyCharm
          # File       : test.py
          # Time       : 2021/5/29 7:57
          from requests_html import HTMLSession
          
          if __name__ == '__main__':
              url = 'https://wwww.baidu.com'
              session = HTMLSession()  # 獲取實(shí)例化session對象
              r = session.get(url)    # 這里的請求和requests的幾乎一樣!同樣可以根據(jù)需要添加headers等參數(shù)
          
          • requests_html發(fā)送請求的方式和requests中使用session方式發(fā)送請求幾乎是一樣的,可以對比參考
          • requests_html同樣可以發(fā)送get/post等請求,且可以和requests同樣攜帶headers/data等參數(shù),具體用法參考requests

          解析響應(yīng)獲

          • 接上,我們需要將獲取的響應(yīng)解析獲取html頁面,在這里我們同樣可以使用requests中的r.content.decode()等原方法!
          • 但是在requests_html中還提供了更便捷的方法:r.html.html
          • r.html.html實(shí)際上是使用了requests_html中的HTML類(負(fù)責(zé)對HTML進(jìn)行解析)來進(jìn)行解析!如下
          #!/usr/bin/env python3
          # coding     : utf-8
          # Author     : xiao qiang
          # 微信公眾號   : xiaoqiangclub
          # Software   : PyCharm
          # File       : test.py
          # Time       : 2021/5/29 7:57
          from requests_html import HTMLSession
          
          if __name__ == '__main__':
              url = 'https://wwww.baidu.com'
              session = HTMLSession()  # 獲取實(shí)例化session對象
              r = session.get(url)  # 這里的請求和requests的幾乎一樣!同樣可以根據(jù)需要添加headers等參數(shù)
          
              # 獲取html頁面
              # html = r.content.decode()  # requests方式
              get_html = r.html.html  # requests_html中的方法
              print(get_html[:15], '...')
          
          • 運(yùn)行結(jié)果(這里只顯示了部分結(jié)果!)

          獲取需要的內(nèi)容

          快速獲取鏈接

          • requests_html中提供了快速獲取網(wǎng)址鏈接的方法
          • 使用linksabsolute_links兩個(gè)屬性分別可以返回HTML對象所包含的所有鏈接和絕對鏈接(均不包含錨點(diǎn))
          # 快速獲取鏈接
          pprint(r.html.links)  # 獲取html中的鏈接(href屬性)
          pprint(r.html.absolute_links)  # 會自動拼接url生成絕對鏈接
          
          • 部分運(yùn)行結(jié)果如下

          獲取元素

          • requests_html中的HTML對象可以直接使用xpathcss選擇器

          使用xpath

          • requests_html中的HTML對象支持xpath語法,它有以下幾個(gè)參數(shù):
          def xpath(self, selector: str, *, clean: bool = False, first: bool = False, _encoding: str = None) -> _XPath:
          - selector,要用的 xpath選擇器;
          - clean,布爾值,如果為True,會清除HTML中style和script標(biāo)簽;
          - first,布爾值,如果為True,會返回第一個(gè)元素,否則會返回滿足條件的元素列表;
          - _encoding,編碼格式。
          
          • 接上面的例子!使用獲取到的響應(yīng)得到HTML對象r.html
          pprint(r.html.xpath('//li[@class="hotsearch-item odd"]/a'))
          pprint(r.html.xpath('//li[@class="hotsearch-item odd"]/a', first=True).text)
          
          • 運(yùn)行結(jié)果
          • xpath語法

          使用css選擇器(find方法)

          • requests_html中的HTML對象支持css選擇器,它有以下幾個(gè)參數(shù):
          def find(self, selector: str = "*", *, containing: _Containing = None, clean: bool = False, first: bool = False, _encoding: str = None) -> _Find:
          - selector,要用的CSS選擇器;
          - clean,布爾值,如果為True,會清除HTML中style和script標(biāo)簽;
          - containing,如果設(shè)置該屬性,只返回包含該屬性文本的標(biāo)簽;
          - first,布爾值,如果為True,會返回第一個(gè)元素,否則會返回滿足條件的元素列表;
          - _encoding,編碼格式。
          
          • 接上面的例子!使用獲取到的響應(yīng)得到HTML對象r.html
          pprint(r.html.find('a.mnav'))
          pprint(r.html.find('a.mnav', first=True).text)
          
          • 運(yùn)行結(jié)果
          • css選擇器語法
          • 可以使用text屬性來獲取元素的文本內(nèi)容

          pprint(r.html.find('a.mnav')[0].text)

          • 執(zhí)行結(jié)果
          • 如果要獲取元素的attribute,用attrs屬性

          pprint(r.html.find('a.mnav')[0].attrs)

          • 執(zhí)行結(jié)果
          • 獲取到attrs屬性后,就可以使用字典的相關(guān)方法獲取內(nèi)容了!
          • 最后還可以使用html屬性獲取一個(gè)元素的html代碼,如下

          pprint(r.html.find('a.mnav')[0].html)

          • 執(zhí)行結(jié)果

          正則搜索(search、search_all)

          • requests_html除了上面的方式還可以使用search/search_all來直接搜索內(nèi)容,返回的是一個(gè)Result對象/Result對象列表實(shí)際上是作者將re正則進(jìn)行了封裝)!
          def search(self, template: str) -> Result:
          # 只有一個(gè)參數(shù)
          template: 就是要檢索的內(nèi)容,這里使用英文狀態(tài)的 {} 來獲取內(nèi)容,有點(diǎn)類似正則里面的 ()
          
          • 使用英文狀態(tài)的 {} 來獲取內(nèi)容,如下
              ret = r.html.find('a.mnav')[0].search('新{}')
              pprint(ret)
              pprint(type(ret))
              pprint(ret[0])
          
          • 執(zhí)行結(jié)果
          • search()獲取到的是第一個(gè)匹配的對象,而searchh_all()則是獲取所有匹配的對象,得到的是一個(gè)列表,如下

          ret = r.html.find('a.mnav')[0].search_all('新{}')
          pprint(ret)
          pprint(type(ret))
          pprint(ret[0][0])

          • 運(yùn)行結(jié)果
          • 除了對某個(gè)元素進(jìn)行檢索外,還可以直接對html對象進(jìn)行搜索,如下

          ret = r.html.search_all('百度{}')
          pprint(ret)
          pprint(type(ret))
          pprint(ret[0][0])

          • 運(yùn)行結(jié)果
          • requests_html內(nèi)容提取的方式這么多,大家可以根據(jù)需要和習(xí)慣選擇使用!

          search補(bǔ)充

          • 在上面提到的search()/search_all()方法中,我們設(shè)定的template參數(shù)可以有多個(gè)取值(多個(gè){}),得到的結(jié)果是一個(gè)列表,我們可以遍歷別表進(jìn)行取值 取值的時(shí)候可以通過result[索引]的方式進(jìn)行獲取對應(yīng)的數(shù)據(jù),如下(示例部分代碼) search_ret = r.html.search_all('<a h{}f="{}"',)
            print(search_ret)
            for ret in search_ret:
            print(ret)
            print(ret[1])
            print('--------------')
            運(yùn)行結(jié)果(部分)
          • 除此之外,我們還可以對取值進(jìn)行命名,返回的結(jié)果是可以使用類似字典(不是字典)[name]的方式取值(不能使用get),如下示例(部分代碼) search_ret = r.html.search_all('<a h{test}f="{url}"',)
            print(search_ret)
            for ret in search_ret:
            print(ret)
            print(ret['name'])
            print('--------------')
          • 運(yùn)行結(jié)果(部分)
          • 以上就是對requests_html模塊search()/search_all()方法的補(bǔ)充內(nèi)容!

          HTML類

          • requests_html中使用HTML類負(fù)責(zé)對HTML進(jìn)行解析
          • HTML類不僅可以解析網(wǎng)絡(luò)請求獲取的響應(yīng),還可以直接解析html文本,如下
          >>> from requests_html import HTML
          >>> doc = """<a href='https://www.baidu.com'>"""
          
          >>> html = HTML(html=doc)
          >>> html.links
          {'https://www.baidu.com'}
          
          • 還可以直接渲染JS,如下
          # 和上面一段代碼接起來
          >>> script = """
                  () => {
                      return {
                          width: document.documentElement.clientWidth,
                          height: document.documentElement.clientHeight,
                          deviceScaleFactor: window.devicePixelRatio,
                      }
                  }
              """
          >>> val = html.render(script=script, reload=False) # render()方法 后面會講
          
          >>> print(val)
          {'width': 800, 'height': 600, 'deviceScaleFactor': 1}
          
          >>> print(html.html)
          <html><head></head><body><a href="https://www.baidu.com"></a></body></html>
          

          高級功能

          • 前面介紹的是requests_htmlrequests庫的基礎(chǔ)上整合的html解析&數(shù)據(jù)篩選的功能!
          • 下面要為大家介紹的是requests_html模塊中的一些高級功能:自動渲染JS&智能分頁

          JS渲染

          • 我們在做爬蟲項(xiàng)目的時(shí)候會遇到網(wǎng)站的頁面是由js生成的情況!這個(gè)時(shí)候要么就是自己去一步一步地分析請求,要么就是使用selenium等第三方庫來進(jìn)行渲染頁面,為了解決這個(gè)難題,requests_html模塊中引進(jìn)了pyppeteer,使用pyppeteer可以像使用selenium一樣實(shí)現(xiàn)網(wǎng)站的完整加載!而且pyppeteer是一個(gè)異步模塊!效率會更高!
          • requests_html模塊在HTML對象的基礎(chǔ)上使用render()方法重新加載js頁面
          • 注意:在第一次使用render()的時(shí)候,系統(tǒng)在用戶目錄(默認(rèn)是~/.pyppeteer/)中下載一個(gè)chromium。下載過程只在第一次執(zhí)行,以后就可以直接使用chromium來執(zhí)行任務(wù)了。在沒有科學(xué)上網(wǎng)的環(huán)境下可能下載速度有點(diǎn)慢,請耐心等待...
          • 下面是一個(gè)官方示例
          >>> r = session.get('http://python-requests.org/')
          
          >>> r.html.render()
          [W:pyppeteer.chromium_downloader] start chromium download.
          Download may take a few minutes.
          [W:pyppeteer.chromium_downloader] chromium download done.
          [W:pyppeteer.chromium_downloader] chromium extracted to: C:\Users\xxxx\.pyppeteer\local-chromium\571375
          >>> r.html.search('Python 2 will retire in only {months} months!')['months']
          '<time>25</time>'
          
          • requests_html模塊在HTML對象的基礎(chǔ)上使用render()方法重新加載js頁面,它有以下幾個(gè)參數(shù):
          def render(self, retries: int = 8, script: str = None, wait: float = 0.2, scrolldown=False, sleep: int = 0, reload: bool = True, timeout: Union[float, int] = 8.0, keep_page: bool = False):
          - retries: 加載頁面失敗的次數(shù)
          - script: 頁面上需要執(zhí)行的JS腳本(可選)
          - wait: 加載頁面前等待的時(shí)間(秒),防止超時(shí)(可選)
          - scrolldown: 頁面向下滾動的次數(shù)(整數(shù))
          - sleep: 在頁面初次渲染之后的等待時(shí)間
          - reload: 如果為False,那么頁面不會從瀏覽器中加載,而是從內(nèi)存中加載,只有設(shè)置為True才會在瀏覽器中渲染JS
          - keep_page: 如果為True,允許您使用 r.html.page 訪問瀏覽器頁面
          
          • requests_html還支持異步渲染JS[^1]

          自動翻頁(不太好用)

          • 很多網(wǎng)站會出現(xiàn)翻頁的情況,requests_html模塊的HTML對象中提供了一個(gè)next()方法來實(shí)現(xiàn)自動翻頁!
          • requests_html模塊在HTML對象的基礎(chǔ)上使用next()方法來實(shí)現(xiàn)自動翻頁!它有以下幾個(gè)參數(shù):
          def next(self, fetch: bool = False, next_symbol: _NextSymbol = DEFAULT_NEXT_SYMBOL) -> _Next:
          fetch: 一個(gè)布爾型參數(shù),默認(rèn)為False:直接返回下一頁的 url地址;
                 如果設(shè)置為True:則直接返回下一頁的 HTML對象
          
          • 這個(gè)方法我自己測試了一下,只有一些特定的網(wǎng)站才能實(shí)現(xiàn)這個(gè)功能,requests_html在的源碼中可以看到,作者通過搜索包含'next', 'more', 'older'字段的a標(biāo)簽(因?yàn)橐话闱闆r下我們的下一頁url就是在a標(biāo)簽下的href屬性中),所以只有滿足了他的條件才會實(shí)現(xiàn)這個(gè)功能(也就是說HTML頁面不按照這個(gè)套路它就無法實(shí)現(xiàn)這個(gè)功能!),下面是部分源碼
          DEFAULT_NEXT_SYMBOL = ['next', 'more', 'older']
          # next()方法
              def next(self, fetch: bool = False, next_symbol: _NextSymbol = DEFAULT_NEXT_SYMBOL) -> _Next:
                  """Attempts to find the next page, if there is one. If ``fetch``
                  is ``True`` (default), returns :class:`HTML <HTML>` object of
                  next page. If ``fetch`` is ``False``, simply returns the next URL.
          
                  """
          
                  def get_next():
                      candidates = self.find('a', containing=next_symbol) # 尋找 包含字段'next', 'more', 'older' 的a標(biāo)簽
          
          • 這里我就不做舉例了,大家可以自行去嘗試!

          異步

          • requests_html中還支持了異步功能
          • requests_html是使用了asynico來封裝了一些異步操作,所以這里的一些操作會用到asynico庫相關(guān)的一些知識,如果您還不太了解,請自行學(xué)習(xí)!

          異步渲染JS

          • 前面我們介紹了使用render()方法渲染JS,其實(shí)它還支持異步渲染
          • 一般異步渲染使用在我們有多個(gè)頁面需要進(jìn)行渲染的情況下,因?yàn)橹灰诙嗳蝿?wù)的時(shí)候才能體現(xiàn)出異步的高效率,它有以下幾個(gè)參數(shù):
          def __init__(self, loop=None, workers=None, mock_browser: bool = True, *args, **kwargs):
          loop: 使用的Asyncio循環(huán)。
          workers: 用于執(zhí)行異步調(diào)用的線程數(shù)量。如果不傳遞,它將默認(rèn)為電腦處理器數(shù)量乘以5
          
          • 更多的異步使用方法請參考asyncio庫的使用方法,下面是一個(gè)官方示例
          >>> async def get_pyclock():
          ...     r = await asession.get('https://pythonclock.org/')
          ...     await r.html.arender()
          ...     return r
          ...
          >>> results = asession.run(get_pyclock, get_pyclock, get_pyclock) # 這里作者將同一個(gè)頁面使用異步方式進(jìn)行了3次渲染,但是實(shí)際上使用的時(shí)間并不是平時(shí)的3倍!可能只是比平時(shí)渲染一個(gè)頁面多花了一點(diǎn)時(shí)間而已!這就是異步的好處!
          
          • 注意:results是一個(gè)列表

          異步傳參

          • 這里是后面加的內(nèi)容,因?yàn)槲彝蝗幌氲接袝r(shí)候我們的函數(shù)有可能是帶參數(shù)的,那么這個(gè)時(shí)候我們可以使用lambda來進(jìn)行傳參,看下面示例
          #!/usr/bin/env python
          # -*- encoding: utf-8 -*-                            
          # @Author     : xiao qiang
          # @WeChat     : xiaoqiangclub                              
          # @Software   : PyCharm      
          # @File       : test002.py
          # @Time       : 2021/5/30 19:48
          from requests_html import AsyncHTMLSession
          
          aSession = AsyncHTMLSession()
          
          
          async def test(tt, yy):
              r = await aSession.get('https://www.baidu.com/')
              await r.html.arender()
              print('-{}-{}-'.format(tt, yy))
              return r
          
          
          ret1 = aSession.run(lambda: test('1', 'a'))
          ret2 = aSession.run(lambda: test('2', 'b'))
          ret3 = aSession.run(lambda: test('3', 'c'))
          print(ret1)
          print(ret2)
          print(ret3)
          
          • 注意:這里ret1/ret2/ret3都是列表
          • 運(yùn)行結(jié)果
          • 上面的示例還可以這樣寫

          #!/usr/bin/env python
          # -*- encoding: utf-8 -*-
          # @Author : xiao qiang
          # @WeChat : xiaoqiangclub
          # @Software : PyCharm
          # @File : test002.py
          # @Time : 2021/5/30 19:48
          from requests_html import AsyncHTMLSession
          aSession = AsyncHTMLSession()
          async def test(tt, yy):
          r = await aSession.get('https://www.baidu.com/')
          await r.html.arender()
          print('-{}-{}-'.format(tt, yy))
          return r
          # ret1 = aSession.run(lambda: test('1', 'a'))
          # ret2 = aSession.run(lambda: test('2', 'b'))
          # ret3 = aSession.run(lambda: test('3', 'c'))
          # print(ret1)
          # print(ret2)
          # print(ret3)
          #
          test_dict = {
          '1': 'a',
          '2': 'b',
          '3': 'c'
          }
          tasks = [lambda i=i, y=y: test(i, y) for i, y in
          test_dict.items()]
          # lambda傳參誤區(qū)參考文章:https://www.jianshu.com/p/58ebd1618556
          ret = aSession.run(*tasks)
          # 注意前面有個(gè) *,不可少!# 參考文章:https://www.jianshu.com/p/58ebd1618556
          print(ret)

          • 這里在使用lambda傳參的時(shí)候可能會出現(xiàn)一個(gè)錯(cuò)誤,可以參考文章解決!
          • 運(yùn)行結(jié)果

          異步發(fā)送請求

          • 我們在做爬蟲的時(shí)候,特別是大型爬蟲的時(shí)候,需要對很多頁面進(jìn)行操作,或者說是需要發(fā)送很多請求,也就是需要進(jìn)行很多IO操作。所以,使用異步發(fā)送請求能顯著地提升我們的爬蟲效率!
          • requests_html模塊中,設(shè)置了一個(gè)AsyncHTMLSession類來實(shí)現(xiàn)發(fā)送異步請求
          • ,下面是一個(gè)官方示例
          >>> from requests_html import AsyncHTMLSession
          >>> asession = AsyncHTMLSession()
          >>> async def get_pythonorg():
          ...     r = await asession.get('https://python.org/')
          ...     return r
          ...
          >>> async def get_reddit():
          ...    r = await asession.get('https://reddit.com/')
          ...    return r
          ...
          >>> async def get_google():
          ...    r = await asession.get('https://google.com/')
          ...    return r
          ...
          >>> results = asession.run(get_pythonorg, get_reddit, get_google)
          >>> results # check the requests all returned a 200 (success) code
          [<Response [200]>, <Response [200]>, <Response [200]>]
          >>> # Each item in the results list is a response object and can be interacted with as such
          >>> for result in results:
          ...     print(result.html.url)
          ...
          https://www.python.org/
          https://www.google.com/
          https://www.reddit.com/
          
          • 上面的示例用到了asynico庫中一些相關(guān)的知識,如果您還不太了解,請自行學(xué)習(xí)!

          總結(jié)

          • requests_html模塊requests庫的基礎(chǔ)上封裝了頁面解析數(shù)據(jù)清理的功能,并且添加了對當(dāng)前比較流行的異步操作,讓我們在做爬蟲項(xiàng)目(一般項(xiàng)目)的時(shí)候無需再去使用多個(gè)第三方模塊來實(shí)現(xiàn)功能,幾乎是提供了一站式的服務(wù)!
          • 所以Python寫爬蟲使用requests_html就對了!(當(dāng)然大項(xiàng)目還是首選scrapy,個(gè)人愚見!)
          • 更多內(nèi)容

          視頻講解源碼

          from requests_html import HTMLSession, HTML, AsyncHTMLSession
          from pprint import pprint
          
          
          class DouBanTest:
              def __init__(self):
                  self.start_url = 'https://movie.douban.com/chart'  # 豆瓣電影排行榜url
                  self.js_url = 'https://movie.douban.com/explore#!type=movie&tag=%E7%83%AD%E9%97%A8&sort=recommend&page_limit=20&page_start=0'
                  self.session = HTMLSession()  # 實(shí)例化session
                  self.aSession = AsyncHTMLSession()  # 實(shí)例化異步session
                  self.headers = {
                      'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36'
                  }
          
              def get_response(self, url):
                  """獲取響應(yīng),并返回requests_html中的HTML對象"""
                  r = self.session.get(url, headers=self.headers)
                  # print(r)
          
                  return r.html
          
              # 快速獲取頁面中的url
              def fast_get_urls(self):
                  """快速獲取頁面中的url"""
                  html = self.get_response(self.start_url)
          
                  # HTML的 links屬性 可以快速獲取到頁面中 a標(biāo)簽中的href屬性
                  urls = html.links
                  # pprint(urls)
          
                  # HTML的 absolute_links屬性 可以快速獲取到頁面中 a標(biāo)簽中的href屬性,并返回絕對url地址
          
                  absolute_urls = html.absolute_links
                  pprint(absolute_urls)
          
              # 清洗數(shù)據(jù)(提取數(shù)據(jù))
              def get_data_by_xpath(self):
                  """使用xpath獲取數(shù)據(jù)"""
                  html = self.get_response(self.start_url)
                  a_list = html.xpath('//table//div/a')
                  # pprint(a_list)
          
                  # 提取它的標(biāo)題和url
                  movies_info = dict()
                  for a in a_list:
                      title = a.text  # 獲取標(biāo)題(文本)
                      # print(title)
                      movie_url = a.attrs.get('href')  # 使用 attrs 來解析element元素,并獲得一個(gè)字典
                      # print(movie_url)
                      # print('-----')
                      movies_info[title] = movie_url
          
                  pprint(movies_info)
          
              # 清洗數(shù)據(jù)(提取數(shù)據(jù))
              def get_data_by_css(self):
                  """使用css獲取數(shù)據(jù)"""
                  html = self.get_response(self.start_url)
                  a_list = html.find('tr[class="item"] div a')  # 參考 css選擇器 語法
                  # pprint(a_list)
          
                  # 提取它的標(biāo)題和url
                  movies_info = dict()
                  for a in a_list:
                      title = a.text  # 獲取標(biāo)題(文本)
                      # print(title)
                      movie_url = a.attrs.get('href')  # 使用 attrs 來解析element元素,并獲得一個(gè)字典
                      # print(movie_url)
                      # print('-----')
                      movies_info[title] = movie_url
          
                  pprint(movies_info)
          
              # 清洗數(shù)據(jù)(提取數(shù)據(jù))
              def get_data_by_re(self):
                  """使用css獲取數(shù)據(jù)"""
                  html = self.get_response(self.start_url)
          
                  # search() 獲取第一條匹配的數(shù)據(jù)
                  # first_url = html.search('a href="{}"')  # 參數(shù)可以參考正則,獲取第一條匹配的數(shù)據(jù)
                  # pprint(first_url)
          
                  # search_all() 獲取所有滿足條件的數(shù)據(jù)列表
                  # url_list = html.search_all('a h{}f="{}"')
                  url_list = html.search_all('a h{title}f="{url}"')  # 對取值方式進(jìn)行命名,返回一個(gè)列表
          
                  # pprint(url_list)
                  #
                  # 提取數(shù)據(jù)
                  for url in url_list:
                      print(url)
                      print(url['title'])  # 使用 result[name] 進(jìn)行取值
                      print(url['url'])
                      # print(url[0])
                      # print(url[1])
                      print('----------')
          
              # HTML類
              def use_HTML(self):
                  """使用HTML模塊處理文檔"""
                  html_str = '<a class="nbg" href="https://movie.douban.com/subject/3099221/" title="活死人軍團(tuán)">'
                  html = HTML(html=html_str)
          
                  # links
                  print(html.links)
          
                  # search()
                  print(html.search('href="{}"'))
          
              # 加載JS頁面
              def load_js(self):
                  html = self.get_response(self.js_url)
          
                  # 使用一個(gè) render()方法 來加載js(實(shí)際上使用這個(gè)pyppeteer)
                  # html.render(wait=3)  # js加載
                  print(html.html)
          
              async def send_requests_ues_async(self, url):
                  """發(fā)送異步請求"""
          
                  """獲取響應(yīng),并返回requests_html中的HTML對象"""
                  r = await self.aSession.get(url, headers=self.headers)
                  # print(r)
          
                  return r.html
          
              def get_response_by_async(self):
                  url_list = [
                      'https://www.baidu.com',
                      'https://www.qq.com',
                      'https://www.163.com',
                  ]
                  tasks = [lambda url=url: self.send_requests_ues_async(url) for url in url_list]
                  ret = self.aSession.run(*tasks)  # 返回的是一個(gè)HTML對象列表
                  # print(ret)
                  # print(ret[0].html)
                  for html in ret:
                      print(html)
          
              async def load_js_use_async(self, url):
                  """異步加載js"""
                  html = await self.send_requests_ues_async(url)
          
                  # 異步加載js
                  await html.arender()
          
                  return html
          
              def get_js_by_async(self):
                  # ret = self.aSession.run(self.load_js_use_async)
                  #
                  # print(ret[0].html)
          
                  url_list = [
                      'https://www.baidu.com',
                      'https://www.qq.com',
                      'https://www.163.com',
                  ]
                  tasks = [lambda url=url: self.load_js_use_async(url) for url in url_list]
                  ret = self.aSession.run(*tasks)  # 返回的是一個(gè)HTML對象列表
                  # print(ret)
                  # print(ret[0].html)
                  for html in ret:
                      print(html)
          
          
          if __name__ == '__main__':
              test = DouBanTest()
              # test.get_data_by_xpath()
              # test.get_data_by_css()
              # test.fast_get_urls()
              # test.get_data_by_re()
              # test.use_HTML()
              # test.load_js()
              # test.get_response_by_async()
              test.get_js_by_async()
          
          

          【本文由 "XiaoqiangClub" 發(fā)布,2021年6月17日】

          要使用的是wkhtmltopdf的Python封裝——pdfkit

          安裝

          1. Install python-pdfkit:

          $ pip install pdfkit

          2. Install wkhtmltopdf:

          • Debian/Ubuntu:

          $ sudo apt-get install wkhtmltopdf

          • Redhat/CentOS

          sudo yum intsall wkhtmltopdf

          • MacOS

          brew install Caskroom/cask/wkhtmltopdf

          使用

          一個(gè)簡單的例子:

          import pdfkit

          pdfkit.from_url('http://google.com', 'out.pdf')

          pdfkit.from_file('test.html', 'out.pdf')

          pdfkit.from_string('Hello!', 'out.pdf')

          你也可以傳遞一個(gè)url或者文件名列表:

          pdfkit.from_url(['google.com', 'yandex.ru', 'engadget.com'], 'out.pdf')

          pdfkit.from_file(['file1.html', 'file2.html'], 'out.pdf')

          也可以傳遞一個(gè)打開的文件:

          with open('file.html') as f:

          pdfkit.from_file(f, 'out.pdf')

          如果你想對生成的PDF作進(jìn)一步處理, 你可以將其讀取到一個(gè)變量中:

          # 設(shè)置輸出文件為False,將結(jié)果賦給一個(gè)變量

          pdf = pdfkit.from_url('http://google.com', False)

          你可以制定所有的 wkhtmltopdf 選項(xiàng) http://wkhtmltopdf.org/usage/wkhtmltopdf.txt. 你可以移除選項(xiàng)名字前面的 '--' .如果選項(xiàng)沒有值, 使用None, False or * 作為字典值:

          options = {

          'page-size': 'Letter',

          'margin-top': '0.75in',

          'margin-right': '0.75in',

          'margin-bottom': '0.75in',

          'margin-left': '0.75in',

          'encoding': "UTF-8",

          'no-outline': None

          }

          pdfkit.from_url('http://google.com', 'out.pdf', options=options)

          默認(rèn)情況下, PDFKit 將會顯示所有的 wkhtmltopdf 輸出. 如果你不想看到這些信息,你需要傳遞一個(gè) quiet 選項(xiàng):

          options = {

          'quiet': ''

          }

          pdfkit.from_url('google.com', 'out.pdf', options=options)

          由于wkhtmltopdf的命令語法 , TOC 和 Cover 選項(xiàng)必須分開指定:

          toc = {

          'xsl-style-sheet': 'toc.xsl'

          }

          cover = 'cover.html'

          pdfkit.from_file('file.html', options=options, toc=toc, cover=cover)

          當(dāng)你轉(zhuǎn)換文件、或字符串的時(shí)候,你可以通過css選項(xiàng)指定擴(kuò)展的 CSS 文件。

          # 單個(gè) CSS 文件

          css = 'example.css'

          pdfkit.from_file('file.html', options=options, css=css)

          # Multiple CSS files

          css = ['example.css', 'example2.css']

          pdfkit.from_file('file.html', options=options, css=css)

          你也可以通過你的HTML中的meta tags傳遞任意選項(xiàng):

          body = """

          <html>

          <head>

          <meta name="pdfkit-page-size" content="Legal"/>

          <meta name="pdfkit-orientation" content="Landscape"/>

          </head>

          Hello World!

          </html>

          """

          pdfkit.from_string(body, 'out.pdf') #with --page-size=Legal and --orientation=Landscape

          配置

          每個(gè)API調(diào)用都有一個(gè)可選的參數(shù)。這應(yīng)該是pdfkit.configuration()API 調(diào)用的一個(gè)實(shí)例. 采用configuration 選項(xiàng)作為初始化參數(shù)。可用的選項(xiàng)有:

          • wkhtmltopdf ——wkhtmltopdf二進(jìn)制文件所在的位置。默認(rèn)情況下pdfkit 會嘗試使用which (在類UNIX系統(tǒng)中) 或 where (在Windows系統(tǒng)中)來判斷
          • meta_tag_prefix -- pdfkit的前綴指定 meta tags(元標(biāo)簽) - 默認(rèn)情況是pdfkit-

          示例 :針對wkhtmltopdf不在系統(tǒng)路徑中(不在$PATH里面)

          PATH里面):

          config = pdfkit.configuration(wkhtmltopdf='/opt/bin/wkhtmltopdf'))

          pdfkit.from_string(html_string, output_file, configuration=config)

          問題

          IOError:'No wkhtmltopdf executable found':

          確保 wkhtmltopdf 在你的系統(tǒng)路徑中(PATH), 會通過 configuration進(jìn)行了配置 (詳情看上文描述)。 在Windows系統(tǒng)中使用where wkhtmltopdf命令 或 在 linux系統(tǒng)中使用 which wkhtmltopdf 會返回 wkhtmltopdf二進(jìn)制可執(zhí)行文件所在的確切位置.

          • IOError: 'Command Failed'

          如果出現(xiàn)這個(gè)錯(cuò)誤意味著 PDFKit不能處理一個(gè)輸入。你可以嘗試直接在錯(cuò)誤信息后面直接運(yùn)行一個(gè)命令來查看是什么導(dǎo)致了這個(gè)錯(cuò)誤 (某些版本的 wkhtmltopdf會因?yàn)槎五e(cuò)誤導(dǎo)致處理失敗)

          • 正常生成,但是出現(xiàn)中文亂碼

          確保兩項(xiàng):

          1)、你的系統(tǒng)中有中文字體

          2)、在html中加入

          下面是我隨便寫的一個(gè)HTML表格:

          <html>

          <head><meta charset="UTF-8"></head>

          <body>

          <table width="400" border="1">

          <tr>

          <th align="left">Item....</th>

          <th align="right">1</th>

          </tr>

          <tr>

          <td align="left">衣服</td>

          <td align="right">1.10</td>

          </tr>

          <tr>

          <td align="left">化妝品</td>

          <td align="right">.00</td>

          </tr>

          <tr>

          <td align="left">食物</td>

          <td align="right">0.40</td>

          </tr>

          <tr>

          <th align="left">tOTAL</th>

          <th align="right">01.50</th>

          </tr>

          </table>

          </body>

          </html>

          下面是生成的PDF截圖

          用正則表達(dá)式

          正則表達(dá)式                                                              描述                                    匹配結(jié)果
          \d+(\.\d*)?                                                       任意整數(shù)和浮點(diǎn)數(shù)                            0.004 2 75.
          
          \b[^\Wa-z0-9_][^\WA-Z0-9_]*\b                                      首字母只能大寫                               Boo Foo
          
          ^http:\/\/([\w-]+(\.[\w-]+)+(\/[\w-.\/\?%&=\u4e00-\u9fa5]*)?)?$      驗(yàn)證網(wǎng)址                           http://www.baidu.com/?id=1
          
          ^[\u4e00-\u9fa5]{
           0,}$                                                驗(yàn)證漢字                                 漢字漢字
          
          \w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*                       驗(yàn)證電子郵件                             example@163.com
          
          ^[1-9]([0-9]{
           16}|[0-9]{
           13})[xX0-9]$                                 驗(yàn)證身份證                            14525419951215445X
          
          ^13[0-9]{
           1}[0-9]{
           8}|^15[9]{
           1}[0-9]{
           8}                               驗(yàn)證手機(jī)號                             138459572***
          
          ^(25[0-5]|2[0-4][0-9]|[0-1]{
           1}[0-9]{
           2}|[1-9]{
           1}[0-9]{
           1}|[1-9])          驗(yàn)證IP                             192.168.1.1
          \.(25[0-5]|2[0-4][0-9]|[0-1]{
           1}[0-9]{
           2}|[1-9]{
           1}[0-9]{
           1}|[1-9]|0)
          \.(25[0-5]|2[0-4][0-9]|[0-1]{
           1}[0-9]{
           2}|[1-9]{
           1}[0-9]{
           1}|[1-9]|0)
          \.(25[0-5]|2[0-4][0-9]|[0-1]{
           1}[0-9]{
           2}|[1-9]{
           1}[0-9]{
           1}|[0-9])$	
          
          ^[a-zA-Z0-9]+([a-zA-Z0-9\-\.]+)?\.s|)$                                驗(yàn)證域名                                baidu.com
          
          ^([a-zA-Z]\:|\\)\\([^\\]+\\)*[^\/:*?"<>|]+\.txt(l)?$               驗(yàn)證文件路徑	                             C:\user\wo
          
          <(.*)>(.*)<\/(.*)>|<(.*)\/>                                        HTML標(biāo)簽匹配(需修改)                          xxxx

          一,必備知識

          1.修飾符(flag)

          re.I  使匹配對大小寫不敏感
          re.L  做本地化識別匹配
          re.M  多行匹配,影響^和$
          re.S  使.匹配包括換行在內(nèi)的所有字符
          re.U  根據(jù)Unicode字符集解析字符.這個(gè)標(biāo)志影響\w \W \b \B
          re.X  該標(biāo)志通過給予你更靈活的格式以便你將正則表達(dá)式寫的更易于理解.
          
          # 例子 result = re.match('^The.*?(\d+).*?phone.', content, re.S)

          2.匹配模式

          ^          匹配字符串開頭
          $          匹配字符串結(jié)尾
          .          匹配人以字符,除了換行符號.當(dāng)re.DOTAALL標(biāo)記被指定時(shí),則可以匹配包括換行符的任意字符.
          [...]      用來表示一組字符,單獨(dú)列出:[amk]匹配a,m或k
          [^...]     不在[]中的字符:[^amk]匹配除amk之外的字符
          re*        匹配0個(gè)或多個(gè)的表達(dá)式
          re+        匹配1個(gè)或多個(gè)的表達(dá)式
          re?        匹配0個(gè)或1個(gè)由前面的正則表達(dá)式定義的片段,非貪婪方式.
          re{
           n}      精準(zhǔn)匹配n個(gè)前面表達(dá)式
          re{
           n,}     匹配大于等于n個(gè)前面表達(dá)式
          re{
           n,m}    匹配n到m個(gè)前面的表達(dá)式定義的片段,貪婪方式
          a|b        匹配a或b
          (re)       對正則表達(dá)式分組,并記住匹配的文本
          (?imx)     正則表達(dá)式包含三種可選標(biāo)志,imx,只影響括號中的區(qū)域.
          (?-imx)    正則表達(dá)式關(guān)閉imx可選標(biāo)志,只影響括號中的區(qū)域.
          (?:re)     類似(...)但不表示一個(gè)組
          (?imx:re)  在括號中使用imx可選標(biāo)志
          (?-imx:re) 在括號中不是用imx可選標(biāo)志
          (?#...)    注釋
          (?=re)     前向肯定界定符.如果所含正則表達(dá)式,以...表示,在當(dāng)前位置成功匹配時(shí)成功,否則失敗.但一旦所含表達(dá)式已經(jīng)嘗試,匹配引擎根本沒有提高,模式的剩余部分還要嘗試界定符右邊.
          (?!re)     前向否定界定符.與肯定界定符相反;當(dāng)所含的表達(dá)式不能在字符串當(dāng)前位置匹配成功時(shí)成功.
          (?>re)     匹配的獨(dú)立模式,省去回朔.
          \w         匹配字符數(shù)字以及下劃線
          \W         匹配非字母數(shù)字下劃線
          \s         匹配任意空白字符,等價(jià)于[\t\n\r\f]
          \S         匹配任意非空白字符
          \d         匹配任意數(shù)字
          \D         匹配任意非數(shù)字
          \A         匹配字符串開始
          \Z         匹配字符串結(jié)束,如果是存在換行,只匹配到換行前的結(jié)束字符串.
          \z         匹配字符串結(jié)束
          \G         匹配最后匹配完成的位置
          \b         匹配一個(gè)單詞邊界,也就是指單詞和空格之間的位置
          \B         匹配非單詞邊界
          \n \t      匹配一個(gè)換行符,一個(gè)制表符
          \1...\9    匹配第n個(gè)分組的內(nèi)容

          3. r的作用

          • r’(\w+) (\w+)(?P.*)’, 字符串之前的r 可以避免 轉(zhuǎn)行符 混淆

          正則表達(dá)式使用 反斜杠 來表示特殊形式,或者把特殊字符 轉(zhuǎn)義成普通字符

          反斜杠 在普通的 Python 字符串 里也有 相同的作用 ,所以就產(chǎn)生了 沖突

          解決辦法是對于正則表達(dá)式樣式使用 Python 的原始字符串表示法;在帶有 ‘r’ 前綴的字符串字面值中, 反斜杠不必做任何特殊處理

          import re
          
          if re.search(r'\\d', 'I have one nee\dle') is not None:
              print('match it',re.search(r'\\d', 'I have one nee\dle').group(0))
          else:
              print('not match')
          
          # match it \d

          4. 貪婪與非貪婪—通配符后面加?

          • 在貪婪匹配下,.* 會 匹配盡可能多的字符
          • 正則表達(dá)式中.*后面是\d+,也就是至少一個(gè)數(shù)字,并沒有指定具體多少個(gè)數(shù)字,因此,.*就盡可能匹配多的字符,這里就把12345匹配了,給\d+留下個(gè)可滿 足條件的數(shù)字 6.最后得到的內(nèi)容就只有數(shù)字6了
          import re
          
          content = 'The 123456 is my one phone number.'
          print('貪婪匹配:')
          result = re.match(r'^The.*(\d+).*', content) #使用match匹配, 第一個(gè)參數(shù)為正則表達(dá)式, 第二個(gè)為要匹配的字符串
          print(result.group()) #輸出匹配內(nèi)容
          print('result = %s'%result.group(1)) #輸出第一個(gè)被()包裹的內(nèi)容
          
          print('-'*20)
          
          print('非貪婪匹配:')
          result = re.match(r'^The.*?(\d+).*', content) 
          print(result.group())
          print('result = %s'%result.group(1))
          
          # 貪婪匹配:
          # The 123456 is my one phone number.
          # result = 6
          # --------------------
          # 非貪婪匹配:
          # The 123456 is my one phone number.
          # result = 123456

          5.轉(zhuǎn)義匹配的使用

          • 由于()屬于正則表達(dá)式的特殊字符, 因此在需要匹配()時(shí), 需要加上轉(zhuǎn)義字符\.
          import re
          
          content = '(百度)www.baidu.com'
          result = re.match('(百度)www.baidu.com', content)
          result2 = re.match('\(百度\)www\.baidu\.com', content)
          if result:
              print(result.group())
          else:
              print('result = None')
          if result2:
              print(result2.group())
          else:
              print('result2 = None')
          
          # result = None
          # (百度)www.baidu.com

          6. group(num = 0)和groups( )

          • group 函數(shù)有一個(gè)int類型的參數(shù),參數(shù)為0表示返回正則匹配的字符串,參數(shù)為1返回正則中第一個(gè)組匹配的內(nèi)容,2返回第二組的內(nèi)容一次類推
          • groups 函數(shù)是所有g(shù)roup函數(shù)結(jié)果組成一個(gè)元組。

          二,函數(shù)應(yīng)用

          1. re.compile(pattern,flags=0)—自定義匹配模板

          • 他的第一個(gè)參數(shù)是正則字符串,第二個(gè)參數(shù)是修飾符
          • 編譯成一個(gè)正則表達(dá)式對象,可以用于匹配match/search/findall 方法 序列
          • 在需要匹配相同正則表達(dá)式情況下, 事先定義一個(gè)compile可以簡化代碼量, 同時(shí)compile中也可以使用修飾符r.S等.
          import re
          
          content1 = '2016-1-1 12:01'
          content2 = '2017-1-1 12:02'
          content3 = '2018-1-1 12:03'
          
          pattern = re.compile('\d{2}:\d{2}')
          result1 = re.sub(pattern, '', content1)
          result2 = re.sub(pattern, '', content2)
          result3 = re.sub(pattern, '', content3)
          print(result1, result2, result3)
          
          #2016-1-1  2017-1-1  2018-1-1 

          2. re.match(pattern,string,flags=0)—從第一個(gè)字符開始匹配

          • 該函數(shù)從第 一個(gè)字符 開始匹配, 如果第一個(gè)對不上,就不可能匹配到數(shù)據(jù)
          • 從第一個(gè)字符開始進(jìn)行匹配,不成功則返回None。
          • 一個(gè)正則表達(dá)式中若有多個(gè)()可用group方法輸出,比如:ex_re=re.match(‘(.?)actor(.?)$’),可用ex_re.group()訪問生成的列表,可用ex_re.group(0)訪問第一個(gè)括號里的元素。
          • pettern 就是正則字符串,如果是通過re.compile方法生成的正則對象.match來調(diào)用的話,就不需要這個(gè)參數(shù)了,因?yàn)檎齽t對象本身就代表了一個(gè)正則匹配模式。
          • string 就是要進(jìn)行匹配的目標(biāo)字符串
          • flags 就是正則的修飾符,比如 re.I
          import re
          regex = '(foo\w)(\w)'
          m = re.match(r'(foo\w)(\w)','fooasdfooasd')
          if m is not None:
              print(m.group(1))
              print(m.groups())
          
          #輸出
          #fooa
          #('fooa', 's')

          3. re.search(pattern,string,flags=0)—不固定開始,但是只匹配第一個(gè)

          • re.search()并不要求必須從字符串的開頭進(jìn)行匹配如match(),也就是說,正則表達(dá)式可以是字符串的一部分。
          • 用法基本與match相同,只不過是掃描整個(gè)字符串,從 一開始匹配到的地方 開始。
          import re
          regex = '<(.*)>(.*)<\/(.*)>|<(.*)\/>'
          m = re.search(regex,"aa<a>aaaa</a>")
          #一樣只有search能匹配到標(biāo)簽
          if m is not None:
              print(m)
              print(m.group())
          
          # 結(jié)果
          # <re.Match object; span=(2, 13), match='<a>aaaa</a>'>
          # <a>aaaa</a>

          4. re.findall(pattern, string,[, flags])—匹配所有數(shù)據(jù)

          • match()和search()都是返回匹配到的 第一個(gè)內(nèi)容就結(jié)束匹配 , findall()是返回 所有 符合匹配規(guī)則的內(nèi)容
          • 搜索字符串內(nèi),以列表形式返回全部能匹配的子串。
          import re
          text = "I am so happy! "
          array1 = re.findall("[a-zA-Z]", text)
          array2 = re.findall("[a-zA-Z]+", text)
          print(array1)
          print(array2)
          
          #結(jié)果
          # ['I', 'a', 'm', 's', 'o', 'h', 'a', 'p', 'p', 'y']
          # ['I', 'am', 'so', 'happy']

          5. finditer(pattern, string,[,flags] )—返回的是匹配到的迭代器對象

          • 與findall()相同,但返回的是一個(gè)迭代器。對于每一次匹配,迭代器都能返回一個(gè)匹配對象
          import re
          regex = 'apple'
          m = [ g.group() for g in re.finditer(regex,'apple1 apple2 apple3')]
          print(m)
          #輸出
          #['apple', 'apple', 'apple']

          6. split(pattern, string, max = 0)

          • 根據(jù)正則表達(dá)式的模式分隔符,split函數(shù)將字符串分割為列表,返回匹配列表,分割最多操作max次
          import re
          list = [
          'aaa,bbb,ccc',
          'ddd,eee,fff',
          ]
          for i in list:
              print(re.split(',',i))
          
          
          # ['aaa', 'bbb', 'ccc']
          # ['ddd', 'eee', 'fff']

          7. re.sub(pattern, repl, string, count=0, flags=0)—可正則的字符串替換

          • 替換字符串中的某些子串,可以用正則表達(dá)式來匹配被選子串。pattern:表示正則表達(dá)式中的模式字符串;
          • repl:被替換的字符串(既可以是字符串,也可以是函數(shù));
          • string:要被處理的,要被替換的字符串;
          • count:匹配的次數(shù), 默認(rèn)是全部替換
          import re
          st = "hello 2019"
          st = re.sub("([0-9]+)","yudengwu",st)
          print(st)
          
          #結(jié)果
          # hello yudengwu

          8. str.replace(old, new,max)—不是re函數(shù),僅用作字符串替換

          • 替代字符串中的某一些子串為另一些字符。
          • old – 將被替換的子字符串。
          • new – 新字符串,用于替換old子字符串。
          • max – 可選字符串, 替換不超過 max 次

          主站蜘蛛池模板: 精品无码av一区二区三区| 加勒比精品久久一区二区三区| 精品久久国产一区二区三区香蕉 | 久久精品成人一区二区三区| 色噜噜一区二区三区| 日本一区二区三区精品国产| 久久精品国产一区二区| 精品深夜AV无码一区二区老年| 精品欧美一区二区在线观看| 国产精品第一区揄拍| 无码人妻精品一区二区三区66| 精品人妻少妇一区二区三区不卡| 精品3d动漫视频一区在线观看| 国产91一区二区在线播放不卡 | 国产香蕉一区二区三区在线视频| 一区二区三区观看免费中文视频在线播放 | 97av麻豆蜜桃一区二区| 一区二区免费在线观看| 无码av免费毛片一区二区| 在线观看国产一区| 日韩精品一区二区三区毛片| 国产成人一区二区三区在线观看| 国产午夜精品一区理论片| 中文字幕人妻丝袜乱一区三区| 人妻无码第一区二区三区| 黄桃AV无码免费一区二区三区 | 国精产品一区一区三区| 国产精品日韩一区二区三区| 国产一区在线mmai| 大伊香蕉精品一区视频在线 | 一区二区在线视频观看| 日韩在线观看一区二区三区| 国产乱人伦精品一区二区| 无码人妻精品一区二区| 一区高清大胆人体| 亚洲综合无码一区二区三区| 中文字幕一区二区免费| 变态拳头交视频一区二区| 精品伦精品一区二区三区视频| 国产伦精品一区二区三区免费迷 | 亚洲线精品一区二区三区|