1. 程式人生 > >Python3中PyQuery的使用(爬蟲利器)

Python3中PyQuery的使用(爬蟲利器)

初始化

像Beautiful Soup一樣,初始化pyquery的時候,也需要傳入HTML文字來初始化一個PyQuery物件。它的初始化方式有多種,比如直接傳入字串,傳入URL,傳入檔名,等等。下面我們來詳細介紹一下。

  • 字串的初始化
html = '''
<div>
    <ul>
         <li class="item-0">first item</li>
         <li class="item-1"><a href="link2.html">second item</a></li>
         <li class="item-0 active"><a href="link3.html"><span class="bold">third item</span></a></li>
         <li class="item-1 active"><a href="link4.html">fourth item</a></li>
         <li class="item-0"><a href="link5.html">fifth item</a></li>
     </ul>
 </div>
'''
from pyquery import PyQuery as pq
doc = pq(html)
print(doc('li'))

和beautifulsoup一樣,可以傳入一段html程式碼,宣告pq的物件之,將初始化的物件傳入CSS選擇器。在這個例項中,我們傳入li節點,這樣就可以選擇所有的li節點。
執行結果為:

<li class="item-0">first item</li>
<li class="item-1"><a href="link2.html">second item</a></li>
<li class="item-0 active"><a href="link3.html"><span class="bold">third item</span></a></li>
<li class="item-1 active"><a href="link4.html">fourth item</a></li>
<li class="item-0"><a href="link5.html">fifth item</a></li>

提取出了所有的li標籤的值。

  • URL初始化
    初始化的引數不僅可以以字串的形式傳遞,還可以傳入網頁的URL,此時只需要指定引數為url即可:
from pyquery import PyQuery as pq
doc = pq(url='https://blog.csdn.net')
print(doc('title'))

這樣的話,PyQuery物件會首先請求這個URL,然後用得到的HTML內容完成初始化,這其實就相當於用網頁的原始碼以字串的形式傳遞給PyQuery類來初始化。
它與以下的功能是相同的:

from pyquery import PyQuery as pq
import requests
doc = pq(requests.get('https://blog.csdn.net').text)
print(doc('title'))
  • 檔案初始化
    除了傳遞url外,也可以傳入本地的檔案進行初始化
from pyquery import PyQuery as pq
doc = pq(filename='demo.html')
print(doc('li'))

本地HTML檔案demo.html,其內容是待解析的HTML字串。這樣它會首先讀取本地的檔案內容,然後用檔案內容以字串的形式傳遞給PyQuery類來初始化。

以上3種初始化方式均可,當然最常用的初始化方式還是以字串形式傳遞。

基本CSS選擇器

  • 具體用法
html = '''
<div id="container">
    <ul class="list">
         <li class="item-0">first item</li>
         <li class="item-1"><a href="link2.html">second item</a></li>
         <li class="item-0 active"><a href="link3.html"><span class="bold">third item</span></a></li>
         <li class="item-1 active"><a href="link4.html">fourth item</a></li>
         <li class="item-0"><a href="link5.html">fifth item</a></li>
     </ul>
 </div>
'''
from pyquery import PyQuery as pq
doc = pq(html)
print(doc('#container .list li'))
print(type(doc('#container .list li')))

這裡我們初始化PyQuery物件之後,傳入了一個CSS選擇器#container .list li,它的意思是先選取id為container的節點,然後再選取其內部的class為list的節點內部的所有li節點。然後,列印輸出。可以看到,我們成功獲取到了符合條件的節點。

最後,將它的型別列印輸出。可以看到,它的型別依然是PyQuery型別。

  • 查詢節點
  • 子節點

查詢子節點時,需要用到find()方法,此時傳入的引數是CSS選擇器。這裡還是以前面的HTML為例:

from pyquery import PyQuery as pq
doc = pq(html)
items = doc('.list')
print(type(items))
print(items)
lis = items.find('li')
print(type(lis))
print(lis)

首先,我們選取class為list的節點,然後呼叫了find()方法,傳入CSS選擇器,選取其內部的li節點,最後列印輸出。可以發現,find()方法會將符合條件的所有節點選擇出來,結果的型別是PyQuery型別。

其實find()的查詢範圍是節點的所有子孫節點,而如果我們只想查詢子節點,那麼可以用children()方法:

lis = items.children()
print(type(lis))
print(lis)

如果要篩選所有子節點中符合條件的節點,比如想篩選出子節點中class為active的節點,可以向children()方法傳入CSS選擇器.active:

lis = items.children('.active')
print(lis)

結果將會作出篩選,留下了class為active的節點。

-父節點
我們可以用parent()方法來獲取某個節點的父節點,示例如下:

html = '''
<div class="wrap">
    <div id="container">
        <ul class="list">
             <li class="item-0">first item</li>
             <li class="item-1"><a href="link2.html">second item</a></li>
             <li class="item-0 active"><a href="link3.html"><span class="bold">third item</span></a></li>
             <li class="item-1 active"><a href="link4.html">fourth item</a></li>
             <li class="item-0"><a href="link5.html">fifth item</a></li>
         </ul>
     </div>
 </div>
'''
from pyquery import PyQuery as pq
doc = pq(html)
items = doc('.list')
container = items.parent()
print(type(container))
print(container)

這裡我們首先用.list選取class為list的節點,然後呼叫parent()方法得到其父節點,其型別依然是PyQuery型別。

這裡的父節點是該節點的直接父節點,也就是說,它不會再去查詢父節點的父節點,即祖先節點。

如果想獲取某個祖先節點,需要使用parents()方法:

from pyquery import PyQuery as pq
doc = pq(html)
items = doc('.list')
parents = items.parents()
print(type(parents))
print(parents)

輸出結果有兩個:一個是class為wrap的節點,一個是id為container的節點。也就是說,parents()方法會返回所有的祖先節點。

如果想要篩選某個祖先節點的話,可以向parents()方法傳入CSS選擇器,這樣就會返回祖先節點中符合CSS選擇器的節點:

parent = items.parents('.wrap')
print(parent)
  • 兄弟節點
    如果要獲取兄弟節點,可以使用siblings()方法。這裡還是以上面的HTML程式碼為例:
from pyquery import PyQuery as pq
doc = pq(html)
li = doc('.list .item-0.active')
print(li.siblings())

這裡首先選擇class為list的節點內部class為item-0和active的節點,也就是第三個li節點。那麼,很明顯,它的兄弟節點有4個,那就是第一、二、四、五個li節點。這裡將會輸出這幾個兄弟節點。

如果要篩選某個兄弟節點,我們依然可以向siblings方法傳入CSS選擇器,這樣就會從所有兄弟節點中挑選出符合條件的節點了:

from pyquery import PyQuery as pq
doc = pq(html)
li = doc('.list .item-0.active')
print(li.siblings('.active'))

這裡我們篩選了class為active的節點,通過剛才的結果可以觀察到,class為active的兄弟節點只有第四個li節點,所以結果應該是一個。

遍歷

剛才可以觀察到,pyquery的選擇結果可能是多個節點,也可能是單個節點,型別都是PyQuery型別,並沒有返回像Beautiful Soup那樣的列表。

對於單個節點來說,可以直接列印輸出,也可以直接轉成字串:

from pyquery import PyQuery as pq
doc = pq(html)
li = doc('.item-0.active')
print(li)
print(str(li))

這兩種處理方式的輸出結果是一樣的。

對於多個節點的結果,我們就需要遍歷來獲取了。例如,這裡把每一個li節點進行遍歷,需要呼叫items()方法:

from pyquery import PyQuery as pq
doc = pq(html)
lis = doc('li').items()
print(type(lis))
for li in lis:
    print(li, type(li))

呼叫items()方法後,會得到一個生成器,遍歷一下,就可以逐個得到li節點物件了,它的型別也是PyQuery型別。每個li節點還可以呼叫前面所說的方法進行選擇,比如繼續查詢子節點,尋找某個祖先節點等,非常靈活。

獲取資訊

提取到節點之後,我們的最終目的當然是提取節點所包含的資訊了。比較重要的資訊有兩類,一是獲取屬性,二是獲取文字,下面分別進行說明。

  • 獲取屬性
    提取到某個PyQuery型別的節點後,就可以呼叫attr()方法來獲取屬性:
html = '''
<div class="wrap">
    <div id="container">
        <ul class="list">
             <li class="item-0">first item</li>
             <li class="item-1"><a href="link2.html">second item</a></li>
             <li class="item-0 active"><a href="link3.html"><span class="bold">third item</span></a></li>
             <li class="item-1 active"><a href="link4.html">fourth item</a></li>
             <li class="item-0"><a href="link5.html">fifth item</a></li>
         </ul>
     </div>
 </div>
'''
from pyquery import PyQuery as pq
doc = pq(html)
a = doc('.item-0.active a')
print(a, type(a))
print(a.attr('href'))

這裡首先選中class為item-0和active的li節點內的a節點,它的型別是PyQuery型別。

然後呼叫attr()方法。在這個方法中傳入屬性的名稱,就可以得到這個屬性值了。

此外,也可以通過呼叫attr屬性來獲取屬性,用法如下:

print(a.attr.href)

兩種方法的結果相同。

如果選中的是多個元素,然後呼叫attr()方法,只會輸出一個節點的結果。

a = doc('a')
print(a, type(a))
print(a.attr('href'))
print(a.attr.href)

這是因為,當返回結果包含多個節點時,呼叫attr()方法,只會得到第一個節點的屬性。
如果入到這種情況,需要對結果進行遍歷:

from pyquery import PyQuery as pq
doc = pq(html)
a = doc('a')
for item in a.items():
    print(item.attr('href'))

這樣就能正常返回所有的結果了。

  • 獲取文字
    獲取節點之後的另一個主要操作就是獲取其內部的文字了,此時可以呼叫text()方法來實現:
html = '''
<div class="wrap">
    <div id="container">
        <ul class="list">
             <li class="item-0">first item</li>
             <li class="item-1"><a href="link2.html">second item</a></li>
             <li class="item-0 active"><a href="link3.html"><span class="bold">third item</span></a></li>
             <li class="item-1 active"><a href="link4.html">fourth item</a></li>
             <li class="item-0"><a href="link5.html">fifth item</a></li>
         </ul>
     </div>
 </div>
'''
from pyquery import PyQuery as pq
doc = pq(html)
a = doc('.item-0.active a')
print(a)
print(a.text())

這裡首先選中一個a節點,然後呼叫text()方法,就可以獲取其內部的文字資訊。此時它會忽略掉節點內部包含的所有HTML,只返回純文字內容。

但如果想要獲取這個節點內部的HTML文字,就要用html()方法了:

from pyquery import PyQuery as pq
doc = pq(html)
li = doc('.item-0.active')
print(li)
print(li.html())

這裡我們選中了第三個li節點,然後呼叫了html()方法,它返回的結果應該是li節點內的所有HTML文字。

這裡同樣有一個問題,如果我們選中的結果是多個節點,text()或html()會返回什麼內容?我們用例項來看一下:

html = '''
<div class="wrap">
    <div id="container">
        <ul class="list">
             <li class="item-1"><a href="link2.html">second item</a></li>
             <li class="item-0 active"><a href="link3.html"><span class="bold">third item</span></a></li>
             <li class="item-1 active"><a href="link4.html">fourth item</a></li>
             <li class="item-0"><a href="link5.html">fifth item</a></li>
         </ul>
     </div>
 </div>
'''
from pyquery import PyQuery as pq
doc = pq(html)
li = doc('li')
print(li.html())
print(li.text())
print(type(li.text())

執行結果如下:

<a href="link2.html">second item</a>
second item third item fourth item fifth item
<class 'str'>

結果可能比較出乎意料,html()方法返回的是第一個li節點的內部HTML文字,而text()則返回了所有的li節點內部的純文字,中間用一個空格分割開,即返回結果是一個字串。

所以這個地方值得注意,如果得到的結果是多個節點,並且想要獲取每個節點的內部HTML文字,則需要遍歷每個節點。而text()方法不需要遍歷就可以獲取,它將所有節點取文字之後合併成一個字串。

  • 節點操作
    pyquery提供了一系列方法來對節點進行動態修改,比如為某個節點新增一個class,移除某個節點等,這些操作有時候會為提取資訊帶來極大的便利。
  • addClass和removeClass
html = '''
<div class="wrap">
    <div id="container">
        <ul class="list">
             <li class="item-0">first item</li>
             <li class="item-1"><a href="link2.html">second item</a></li>
             <li class="item-0 active"><a href="link3.html"><span class="bold">third item</span></a></li>
             <li class="item-1 active"><a href="link4.html">fourth item</a></li>
             <li class="item-0"><a href="link5.html">fifth item</a></li>
         </ul>
     </div>
 </div>
'''
from pyquery import PyQuery as pq
doc = pq(html)
li = doc('.item-0.active')
print(li)
li.removeClass('active')
print(li)
li.addClass('active')
print(li)

首先選中了第三個li節點,然後呼叫removeClass()方法,將li節點的active這個class移除,後來又呼叫addClass()方法,將class添加回來。每執行一次操作,就列印輸出當前li節點的內容。
執行結果如下:

<li class="item-0 active"><a href="link3.html"><span class="bold">third item</span></a></li>
<li class="item-0"><a href="link3.html"><span class="bold">third item</span></a></li>
<li class="item-0 active"><a href="link3.html"><span class="bold">third item</span></a></li>

可以看到,一共輸出了3次。第二次輸出時,li節點的active這個class被移除了,第三次class又添加回來了。

所以說,addClass()和removeClass()這些方法可以動態改變節點的class屬性。

  • attr、text和html
    當然,除了操作class這個屬性外,也可以用attr()方法對屬性進行操作。此外,還可以用text()和html()方法來改變節點內部的內容。示例如下:
html = '''
<ul class="list">
     <li class="item-0 active"><a href="link3.html"><span class="bold">third item</span></a></li>
</ul>
'''
from pyquery import PyQuery as pq
doc = pq(html)
li = doc('.item-0.active')
print(li)
li.attr('name', 'link')
print(li)
li.text('changed item')
print(li)
li.html('<span>changed item</span>')
print(li)

這裡我們首先選中li節點,然後呼叫attr()方法來修改屬性,其中該方法的第一個引數為屬性名,第二個引數為屬性值。接著,呼叫text()和html()方法來改變節點內部的內容。三次操作後,分別列印輸出當前的li節點。
執行結果如下:

<li class="item-0 active"><a href="link3.html"><span class="bold">third item</span></a></li>
<li class="item-0 active" name="link"><a href="link3.html"><span class="bold">third item</span></a></li>
<li class="item-0 active" name="link">changed item</li>
<li class="item-0 active" name="link"><span>changed item</span></li>

可以發現,呼叫attr()方法後,li節點多了一個原本不存在的屬性name,其值為link。接著呼叫text()方法,傳入文字之後,li節點內部的文字全被改為傳入的字串文字了。最後,呼叫html()方法傳入HTML文字後,li節點內部又變為傳入的HTML文字了。

所以說,如果attr()方法只傳入第一個引數的屬性名,則是獲取這個屬性值;如果傳入第二個引數,可以用來修改屬性值。text()和html()方法如果不傳引數,則是獲取節點內純文字和HTML文字;如果傳入引數,則進行賦值。

  • remove()
    remove()方法就是移除,它有時會為資訊的提取帶來非常大的便利。下面有一段HTML文字:
html = '''
<div class="wrap">
    Hello, World
    <p>This is a paragraph.</p>
 </div>
'''
from pyquery import PyQuery as pq
doc = pq(html)
wrap = doc('.wrap')
print(wrap.text())

現在想提取Hello, World這個字串,而不要p節點內部的字串,需要怎樣操作呢?

這裡直接先嚐試提取class為wrap的節點的內容,看看是不是我們想要的。執行結果如下:

Hello, World This is a paragraph.

這個結果還包含了內部的p節點的內容,也就是說text()把所有的純文字全提取出來了。如果我們想去掉p節點內部的文字,可以選擇再把p節點內的文字提取一遍,然後從整個結果中移除這個子串,但這個做法明顯比較煩瑣。

這時remove()方法就可以派上用場了,我們可以接著這麼做:

wrap.find('p').remove()
print(wrap.text())

首先選中p節點,然後呼叫了remove()方法將其移除,然後這時wrap內部就只剩下Hello, World這句話了,然後再利用text()方法提取即可。

另外,其實還有很多節點操作的方法,比如append()、empty()和prepend()等方法,它們和jQuery的用法完全一致,詳細的用法可以參考官方文件:http://pyquery.readthedocs.io/en/latest/api.html。