導包:pip install bs4

import requests
from bs4 import BeautifulSoup

url = 'https://movie.douban.com/top250'

headers = {
    'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36'
}

req = requests.get(url, headers=headers)

# 第一個參數為:html文本,第二個參數為:解析器
soup = BeautifulSoup(req.text, "html.parser")

 常見解析器選擇

  1. html.parser(默認解析器)
  • 適用場景:大多數常規的 HTML 文檔。
  • 優點:不需要安裝額外的庫,速度較快,對於簡單或格式良好的 HTML 頁面非常適合。
  • 缺點:在處理一些不規範的 HTML 時,可能會遇到解析錯誤。
  • 推薦理由:對於大多數普通的網頁抓取任務,html.parser 是一個非常好的默認選擇。
  1. lxml
  • 適用場景:需要快速處理大量 HTML 或 XML 數據,且希望提高性能。
  • 優點:非常快速,支持解析 XML 和 HTML,處理大型文檔時表現優秀。
  • 缺點:需要安裝 lxml 庫。
  • 推薦理由:如果你的應用需要更高的性能,尤其是在處理大規模網頁時,lxml 是首選。
  1. html5lib
  • 適用場景:文檔非常髒亂或格式不規範,需要容錯處理。
  • 優點:支持 HTML5 標準,容錯性極強,能夠正確處理不規範的 HTML 文檔。
  • 缺點:性能較差,解析速度比其他解析器慢。
  • 推薦理由:如果你抓取的網頁非常混亂,或者 HTML 內容不符合標準,html5lib 是最佳選擇。

對於大多數網頁抓取任務,html.parser 足夠用;如果追求性能和速度,選擇 lxml;如果文檔很髒亂,選擇 html5lib

使用教程

soup會把當前req.text的內容根據標籤轉為對象。

find 方法

find 方法用於在當前節點下查找第一個符合條件的子節點,並返回對應的 Tag 對象。如果找不到則返回 None。它的常用參數有:

  • name:標籤名字符串或正則表達式,例如 div"a"
  • attrs:屬性字典,用來匹配標籤屬性,例如 {"class":"item"}
  • recursive:是否遞歸查找,默認為 True;如果設為 False,則只在當前節點的直接子節點裏查找。
  • text:可以傳入字符串、正則表達式或函數,用於匹配標籤包含的文本內容。
  • **kwargs:簡寫方式,也可以直接把任意屬性名作為關鍵字參數傳入,例如 class_="item"

find_all 方法

find_all 方法與find()類似,用於查找所有符合條件的子節點,返回一個列表(list)形式。如果沒有找到任何匹配項,則返回空列表。它支持的參數與 find 完全相同,但通常還會用到以下兩個參數:

  • limit:限制返回結果的數量,例如 limit=3 則最多返回三個匹配項。
  • **kwargs:同樣可以使用簡化形式匹配屬性。

find_all()和find()都支持鏈式編程,例如:

soup.find('div', class_="bd", recursive=True).find('p')
        grade = li.find('div', class_="bd", recursive=True).find('span', class_="rating_num")

兩者對比與使用場景

  • 想要只取第一個匹配結果時,用 find。它返回單個對象,更省內存,也能直接判斷是否存在。
  • 想要一次拿到所有匹配結果時,用 find_all,結果是列表,方便後續遍歷、過濾和統計。
import requests
from bs4 import BeautifulSoup

url = 'https://movie.douban.com/top250'

headers = {
    'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36'
}

req = requests.get(url, headers=headers)

# 第一個參數為:html文本,第二個參數為:解析器
soup = BeautifulSoup(req.text, "html.parser")

CSS選擇器

3. 使用 CSS 選擇器提取元素

在 BeautifulSoup 中,使用 CSS 選擇器的方式非常簡單。我們可以通過 soup.select() 方法來實現。select() 方法接受一個 CSS 選擇器字符串作為參數,返回符合條件的所有元素的列表。

3.1 選擇單個元素

選擇一個特定的 div 元素,該元素的 class 屬性為 example-class:

element = soup.select('div.example-class')

select() 方法返回的是一個列表,即使只有一個元素匹配,也會返回一個列表。要訪問這個元素,可以通過索引:

first_element = soup.select('div.example-class')[0]
print(first_element)

3.2 選擇多個元素

CSS 選擇器支持選擇多個匹配的元素。例如,如果我們想選擇所有 p 標籤內的文本:

# 選擇所有的 p 標籤
 p_elements = soup.select('p')
 
 for p in p_elements:
     print(p.text)

3.3 嵌套選擇器

CSS 選擇器還支持嵌套選擇器。假設我們想選擇一個 div 中所有的 span 標籤:

# 選擇所有 div 元素中的 span 標籤
 span_elements = soup.select('div span')
 
 for span in span_elements:
     print(span.text)

3.4 選擇具有特定屬性的元素

CSS 選擇器也支持根據元素的屬性進行選擇。假設我們想選擇所有具有 id 為 main 的元素:

# 選擇 id 為 main 的元素
 main_element = soup.select('#main')

同樣,可以使用類選擇器來選擇具有特定 class 的元素:

# 選擇所有 class 為 'example-class' 的元素
 example_elements = soup.select('.example-class')

此外,還可以選擇具有特定屬性的元素,例如 href 屬性:

# 選擇所有包含 href 屬性的 a 標籤
 links = soup.select('a[href]')
 for link in links:
     print(link['href'])

3.5 子元素選擇

CSS 選擇器還可以選擇子元素。例如,選擇一個 div 中的第一個 p 標籤:

# 選擇 div 中的第一個 p 標籤
 first_p_in_div = soup.select('div > p')[0]

這裏的 > 符號表示選擇 div 元素下的直接子元素,[0]表示第一個子元素。

3.6 組合選擇器

CSS 選擇器允許將多個選擇器組合在一起。例如,我們可以選擇所有 div 元素中的 a 標籤,且這些 a 標籤有 href 屬性:

# 選擇 div 中包含 href 屬性的 a 標籤
 links_in_div = soup.select('div a[href]')

3.7 使用偽類選擇器

CSS 選擇器還支持一些偽類選擇器,如 :first-child、:last-child 等。比如,選擇所有 ul 元素中的第一個 li 元素:

# 選擇 ul 中的第一個 li 元素
 first_li = soup.select('ul li:first-child')

4. 結合選擇器提取元素的內容

在我們成功選擇到網頁元素後,可以輕鬆提取其內容。常見的提取方法包括 .text(提取文本)、.get('屬性名')(提取屬性)。

4.1 提取文本

# 提取元素中的文本內容
 text_content = soup.select('div.example-class')[0].text
 print(text_content)

4.2 提取屬性

# 提取鏈接的 href 屬性
 link_href = soup.select('a')[0].get('href')
 print(link_href)

5. 進階應用:鏈式選擇器

你可以鏈式使用 CSS 選擇器來精確定位元素。例如,首先選擇一個具有特定 ID 的元素,然後選擇其下的特定子元素:

# 鏈式選擇:選擇 id 為 'main' 的 div 中的第一個 p 標籤
 main_first_p = soup.select('#main > p')[0]

簡單使用示例

爬取豆瓣TOP250首頁電影名稱

import sys

import requests
from bs4 import BeautifulSoup

url = 'https://movie.douban.com/top250'
headers = {
    'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36'
}

req = requests.get(url, headers=headers)
req.encoding = 'UTF-8'

if req.status_code != 200:
    print("請求響應錯誤!")
    sys.exit()

soup = BeautifulSoup(req.text, "html.parser")

# 輸出網站的標題:‘ 豆瓣電影 Top 250 ’
# print(soup.find('title').string)


liSet = soup.find('div', id="wrapper").find('div', id="content").find('ol', class_="grid_view").find_all('li')

for li in liSet:
    # recursive=Ture為默認值,表示遞歸搜索,值為False時只所有子元素,不會遞歸搜索
    t = li.find('div', class_="hd", recursive=True).find('a').find('span', class_="title")

    # t.text 不管標籤內部結構多複雜,都會把標籤自身及所有子孫節點中的可見文本一次性提取出來並串聯返回。
    # t.string 只有當該標籤的內容“非常乾淨”——整個標籤裏只存在唯一一段 NavigableString(而且中間沒有其他子標籤)時,才返回這段 NavigableString。
    print(t.string)

# print(soup.find_all())

爬取豆瓣TOP250所有頁面電影信息

import sys
import re

import requests
from bs4 import BeautifulSoup

headers = {
    'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36'
}

# 當前電影的排行
ranking = 1

# 以"主演","|","..."作為分割 
pattern = r"導演:\s*(?P<dym>.*?)(?=\s*(?:主演|/|\.\.\.))"

for i in range(0, 250, 25):
    url = f'https://movie.douban.com/top250?start={i}&filter='
    req = requests.get(url, headers=headers)
    req.encoding = 'UTF-8'

    if req.status_code != 200:
        print("請求響應錯誤!")
        sys.exit()

    soup = BeautifulSoup(req.text, "html.parser")

    # 輸出網站的標題:‘ 豆瓣電影 Top 250 ’
    # print(soup.find('title').string)

    print(f'訪問URL:{url}')
    liSet = soup.find('div', id="wrapper").find('div', id="content").find('ol', class_="grid_view").find_all('li')
    for li in liSet:
        # recursive=Ture為默認值,表示遞歸搜索,值為False時只所有子元素,不會遞歸搜索
        filmTitle = li.find('div', class_="hd", recursive=True).find('a').find('span', class_="title")
        dy = li.find('div', class_="bd", recursive=True).find('p')
        grade = li.find('div', class_="bd", recursive=True).find('span', class_="rating_num")

        dym = ''
        try:
            # 提取導演名稱
            dym = re.search(pattern, dy.text).group('dym')
        except Exception as e:
            print(e)

        # t.text 不管標籤內部結構多複雜,都會把標籤自身及所有子孫節點中的可見文本一次性提取出來並串聯返回
        # t.string 只有當該標籤的內容“非常乾淨”——整個標籤裏只存在唯一一段 NavigableString(而且中間沒有其他子標籤)時,才返回這段 NavigableString

        print(f"排名:{ranking},電影名:{filmTitle.string},評分:{grade.string},導演:{dym}")
        ranking += 1
    print('\n')

Python爬蟲快速入門,BeautifulSoup基本使用及實踐_CSS