一、分析網(wǎng)頁
以經(jīng)典的爬取豆瓣電影 Top250 信息為例。每條電影信息在 ol class 為 grid_view 下的 li 標(biāo)簽里,獲取到所有 li 標(biāo)簽的內(nèi)容,然后遍歷,就可以從中提取出每一條電影的信息。
翻頁查看url變化規(guī)律:
第1頁:https://movie.douban.com/top250?start=0&filter=
第2頁:https://movie.douban.com/top250?start=25&filter=
第3頁:https://movie.douban.com/top250?start=50&filter=
第10頁:https://movie.douban.com/top250?start=225&filter=
start參數(shù)控制翻頁,start = 25 * (page - 1)
本文分別利用正則表達(dá)式、BeautifulSoup、PyQuery、Xpath來解析提取數(shù)據(jù),并將豆瓣電影 Top250 信息保存到本地。
二、正則表達(dá)式
正則表達(dá)式是一個特殊的字符序列,它能幫助你方便地檢查一個字符串是否與某種模式匹配,常用于數(shù)據(jù)清洗,也可以順便用于爬蟲,從網(wǎng)頁源代碼文本中匹配出我們想要的數(shù)據(jù)。
re.findall
-
在字符串中找到正則表達(dá)式所匹配的所有子串,并返回一個列表,如果沒有找到匹配的,則返回空列表。
-
注意:match和 search 是匹配一次;而 findall 匹配所有。
-
語法格式為:findall(string[, pos[, endpos]])
-
string : 待匹配的字符串;pos : 可選參數(shù),指定字符串的起始位置,默認(rèn)為 0;endpos : 可選參數(shù),指定字符串的結(jié)束位置,默認(rèn)為字符串的長度。
示例如下:
import re
text = """
<div class="box picblock col3" style="width:186px;height:264px">
<img src2="http://pic2.sc.chinaz.com/Files/pic/pic9/202007/apic26584_s.jpg" 123nfsjgnalt="山水風(fēng)景攝影圖片">
<a target="_blank"
<img src2="http://pic2.sc.chinaz.com/Files/pic/pic9/202007/apic26518_s.jpg" enrberonbialt="山脈湖泊山水風(fēng)景圖片">
<a target="_blank"
<img src2="http://pic2.sc.chinaz.com/Files/pic/pic9/202006/apic26029_s.jpg" woenigoigniefnirneialt="旅游景點(diǎn)山水風(fēng)景圖片">
<a target="_blank"
"""
pattern = re.compile(r'\d+') # 查找所有數(shù)字
result1 = pattern.findall('me 123 rich 456 money 1000000000000')
print(result1)
img_info = re.findall('<img src2="(.*?)" .*alt="(.*?)">', text) # 匹配src2 alt里的內(nèi)容
for src, alt in img_info:
print(src, alt)
['123', '456', '1000000000000']
http://pic2.sc.chinaz.com/Files/pic/pic9/202007/apic26584_s.jpg 山水風(fēng)景攝影圖片
http://pic2.sc.chinaz.com/Files/pic/pic9/202007/apic26518_s.jpg 山脈湖泊山水風(fēng)景圖片
http://pic2.sc.chinaz.com/Files/pic/pic9/202006/apic26029_s.jpg 旅游景點(diǎn)山水風(fēng)景圖片
代碼如下:
# -*- coding: UTF-8 -*-
import requests
import re
from pandas import DataFrame
from fake_useragent import UserAgent
import logging
# 日志輸出的基本配置
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s: %(message)s')
# 隨機(jī)產(chǎn)生請求頭
ua = UserAgent(verify_ssl=False, path='fake_useragent.json')
def random_ua():
headers = {
"Accept-Encoding": "gzip",
"Connection": "keep-alive",
"User-Agent": ua.random
}
return headers
def scrape_html(url):
resp = requests.get(url, headers=random_ua())
# print(resp.status_code, type(resp.status_code))
if resp.status_code == 200:
return resp.text
else:
logging.info('請求網(wǎng)頁失敗')
def get_data(page):
url = f"https://movie.douban.com/top250?start={25 * page}&filter="
html_text = scrape_html(url)
# 電影名稱 導(dǎo)演 主演
name = re.findall('<img width="100" alt="(.*?)" src=".*"', html_text)
director_actor = re.findall('(.*?)<br>', html_text)
director_actor = [item.strip() for item in director_actor]
# 上映時間 上映地區(qū) 電影類型信息 去除兩端多余空格
info = re.findall('(.*) / (.*) / (.*)', html_text)
time_ = [x[0].strip() for x in info]
area = [x[1].strip() for x in info]
genres = [x[2].strip() for x in info]
# 評分 評分人數(shù)
rating_score = re.findall('<span class="rating_num" property="v:average">(.*)</span>', html_text)
rating_num = re.findall('<span>(.*?)人評價</span>', html_text)
# 一句話引言
quote = re.findall('<span class="inq">(.*)</span>', html_text)
data = {'電影名': name, '導(dǎo)演和主演': director_actor,
'上映時間': time_, '上映地區(qū)': area, '電影類型': genres,
'評分': rating_score, '評價人數(shù)': rating_num, '引言': quote}
df = DataFrame(data)
if page == 0:
df.to_csv('movie_data2.csv', mode='a+', header=True, index=False)
else:
df.to_csv('movie_data2.csv', mode='a+', header=False, index=False)
logging.info(f'已爬取第{page + 1}頁數(shù)據(jù)')
if __name__ == '__main__':
for i in range(10):
get_data(i)
結(jié)果如下:
三、BeautifulSoup
find( )與 find_all( ) 是 BeautifulSoup 對象的兩個方法,它們可以匹配 html 的標(biāo)簽和屬性,把 BeautifulSoup 對象里符合要求的數(shù)據(jù)都提取出來:
-
find( )只提取首個滿足要求的數(shù)據(jù)
-
find_all( )提取出的是所有滿足要求的數(shù)據(jù)
-
find( ) 或 find_all( ) 括號中的參數(shù):標(biāo)簽和屬性可以任選其一,也可以兩個一起使用,這取決于我們要在網(wǎng)頁中提取的內(nèi)容。括號里的class_,這里有一個下劃線,是為了和 python 語法中的類 class 區(qū)分,避免程序沖突。當(dāng)然,除了用 class 屬性去匹配,還可以使用其它屬性,比如 style 屬性等;只用其中一個參數(shù)就可以準(zhǔn)確定位的話,就只用一個參數(shù)檢索。如果需要標(biāo)簽和屬性同時滿足的情況下才能準(zhǔn)確定位到我們想找的內(nèi)容,那就兩個參數(shù)一起使用。
代碼如下:
# -*- coding: UTF-8 -*-
import requests
from bs4 import BeautifulSoup
import openpyxl
from fake_useragent import UserAgent
import logging
# 日志輸出的基本配置
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s: %(message)s')
# 隨機(jī)產(chǎn)生請求頭
ua = UserAgent(verify_ssl=False, path='fake_useragent.json')
wb = openpyxl.Workbook() # 創(chuàng)建工作簿對象
sheet = wb.active # 獲取工作簿的活動表
sheet.title = "movie" # 工作簿重命名
sheet.append(["排名", "電影名", "導(dǎo)演和主演", "上映時間", "上映地區(qū)", "電影類型", "評分", "評價人數(shù)", "引言"])
def random_ua():
headers = {
"Accept-Encoding": "gzip",
"Connection": "keep-alive",
"User-Agent": ua.random
}
return headers
def scrape_html(url):
resp = requests.get(url, headers=random_ua())
# print(resp.status_code, type(resp.status_code))
if resp.status_code == 200:
return resp.text
else:
logging.info('請求網(wǎng)頁失敗')
def get_data(page):
global rank
url = f"https://movie.douban.com/top250?start={25 * page}&filter="
html_text = scrape_html(url)
soup = BeautifulSoup(html_text, 'html.parser')
lis = soup.find_all('div', class_='item')
for li in lis:
name = li.find('div', class_='hd').a.span.text
temp = li.find('div', class_='bd').p.text.strip().split('\n')
director_actor = temp[0]
temp1 = temp[1].rsplit('/', 2)
time_, area, genres = [item.strip() for item in temp1]
quote = li.find('p', class_='quote')
# 有些電影信息沒有一句話引言
if quote:
quote = quote.span.text
else:
quote = None
rating_score = li.find('span', class_='rating_num').text
rating_num = li.find('div', class_='star').find_all('span')[-1].text
sheet.append([rank, name, director_actor, time_, area, genres, rating_score, rating_num, quote])
logging.info([rank, name, director_actor, time_, area, genres, rating_score, rating_num, quote])
rank += 1
if __name__ == '__main__':
rank = 1
for i in range(10):
get_data(i)
wb.save(filename='movie_info4.xlsx')
結(jié)果如下:
四、PyQuery
-
每個網(wǎng)頁,都有一定的特殊結(jié)構(gòu)和層級關(guān)系,并且很多節(jié)點(diǎn)都有 id 或 class 作為區(qū)分,我們可以借助它們的結(jié)構(gòu)和屬性來提取信息。
-
強(qiáng)大的 HTML 解析庫:pyquery,利用它,我們可以直接解析 DOM 節(jié)點(diǎn)的結(jié)構(gòu),并通過 DOM 節(jié)點(diǎn)的一些屬性快速進(jìn)行內(nèi)容提取。
如下示例:在解析 HTML 文本的時候,首先需要將其初始化為一個 pyquery 對象。它的初始化方式有多種,比如直接傳入字符串、傳入 URL、傳入文件名等等。
from pyquery import PyQuery as pq
html = '''
<div>
<ul class="clearfix">
<li class="item-0">first item</li>
<li class="item-1"><a href="link2.html">second item</a></li>
<li><img src="http://pic.netbian.com/uploads/allimg/210107/215736-1610027856f6ef.jpg"></li>
<li><img src="http://pic.netbian.com//uploads/allimg/190902/152344-1567409024af8c.jpg"></li>
</ul>
</div>
'''
doc = pq(html)
print(doc('li'))
結(jié)果如下:
<li class="item-0">first item</li>
<li class="item-1"><a href="link2.html">second item</a></li>
<li><img src="http://pic.netbian.com/uploads/allimg/210107/215736-1610027856f6ef.jpg"/></li>
<li><img src="http://pic.netbian.com//uploads/allimg/190902/152344-1567409024af8c.jpg"/></li>
首先引入 pyquery 這個對象,取別名為 pq,然后定義了一個長 HTML 字符串,并將其當(dāng)作參數(shù)傳遞給 pyquery 類,這樣就成功完成了初始化。接下來,將初始化的對象傳入 CSS 選擇器。在這個實例中,我們傳入 li 節(jié)點(diǎn),這樣就可以選擇所有的 li 節(jié)點(diǎn)。
代碼如下:
# -*- coding: UTF-8 -*-
import requests
from pyquery import PyQuery as pq
import openpyxl
from fake_useragent import UserAgent
import logging
# 日志輸出的基本配置
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s: %(message)s')
# 隨機(jī)產(chǎn)生請求頭
ua = UserAgent(verify_ssl=False, path='fake_useragent.json')
wb = openpyxl.Workbook() # 創(chuàng)建工作簿對象
sheet = wb.active # 獲取工作簿的活動表
sheet.title = "movie" # 工作簿重命名
sheet.append(["排名", "電影名", "導(dǎo)演和主演", "上映時間", "上映地區(qū)", "電影類型", "評分", "評價人數(shù)", "引言"])
def random_ua():
headers = {
"Accept-Encoding": "gzip",
"Connection": "keep-alive",
"User-Agent": ua.random
}
return headers
def scrape_html(url):
resp = requests.get(url, headers=random_ua())
# print(resp.status_code, type(resp.status_code))
if resp.status_code == 200:
return resp.text
else:
logging.info('請求網(wǎng)頁失敗')
def get_data(page):
global rank
url = f"https://movie.douban.com/top250?start={25 * page}&filter="
html_text = scrape_html(url)
doc = pq(html_text)
lis = doc('.grid_view li')
for li in lis.items():
name = li('.hd a span:first-child').text()
temp = li('.bd p:first-child').text().split('\n')
director_actor = temp[0]
temp1 = temp[1].rsplit('/', 2)
time_, area, genres = [item.strip() for item in temp1]
quote = li('.quote span').text()
rating_score = li('.star .rating_num').text()
rating_num = li('.star span:last-child').text()
sheet.append([rank, name, director_actor, time_, area, genres, rating_score, rating_num, quote])
logging.info([rank, name, director_actor, time_, area, genres, rating_score, rating_num, quote])
rank += 1
if __name__ == '__main__':
rank = 1
for i in range(10):
get_data(i)
wb.save(filename='movie_info3.xlsx')
結(jié)果如下:
五、Xpath
Xpath是一個非常好用的解析方法,同時也作為爬蟲學(xué)習(xí)的基礎(chǔ),在后面的 Selenium 以及 Scrapy 框架中也會涉及到這部分知識。
首先我們使用 lxml 的 etree 庫,然后利用 etree.HTML 初始化,然后我們將其打印出來。其中,這里體現(xiàn)了 lxml 的一個非常實用的功能就是自動修正 html 代碼,大家應(yīng)該注意到了,最后一個 li 標(biāo)簽,其實我把尾標(biāo)簽刪掉了,是不閉合的。不過,lxml 因為繼承了 libxml2 的特性,具有自動修正 HTML 代碼的功能,通過 xpath 表達(dá)式可以提取標(biāo)簽里的內(nèi)容,如下所示:
from lxml import etree
text = '''
<div>
<ul>
<li class="item-0"><a href="link1.html">first item</a></li>
<li class="item-1"><a href="link2.html">second item</a></li>
<li class="item-inactive"><a href="link3.html">third item</a></li>
<li class="item-1"><a href="link4.html">fourth item</a></li>
<li class="item-0"><a href="link5.html">fifth item</a>
</ul>
</div>
'''
html = etree.HTML(text)
result = etree.tostring(html)
result1 = html.xpath('//li/@class') # xpath表達(dá)式
print(result1)
print(result)
['item-0', 'item-1', 'item-inactive', 'item-1', 'item-0']
<html><body>
<div>
<ul>
<li class="item-0"><a href="link1.html">first item</a></li>
<li class="item-1"><a href="link2.html">second item</a></li>
<li class="item-inactive"><a href="link3.html">third item</a></li>
<li class="item-1"><a href="link4.html">fourth item</a></li>
<li class="item-0"><a href="link5.html">fifth item</a></li>
</ul>
</div>
</body></html>
代碼如下:
# -*- coding: UTF-8 -*-
import requests
from lxml import etree
import openpyxl
from fake_useragent import UserAgent
import logging
# 日志輸出的基本配置
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s: %(message)s')
# 隨機(jī)產(chǎn)生請求頭
ua = UserAgent(verify_ssl=False, path='fake_useragent.json')
wb = openpyxl.Workbook() # 創(chuàng)建工作簿對象
sheet = wb.active # 獲取工作簿的活動表
sheet.title = "movie" # 工作簿重命名
sheet.append(["排名", "電影名", "導(dǎo)演和主演", "上映時間", "上映地區(qū)", "電影類型", "評分", "評價人數(shù)", "引言"])
def random_ua():
headers = {
"Accept-Encoding": "gzip",
"Connection": "keep-alive",
"User-Agent": ua.random
}
return headers
def scrape_html(url):
resp = requests.get(url, headers=random_ua())
# print(resp.status_code, type(resp.status_code))
if resp.status_code == 200:
return resp.text
else:
logging.info('請求網(wǎng)頁失敗')
def get_data(page):
global rank
url = f"https://movie.douban.com/top250?start={25 * page}&filter="
html = etree.HTML(scrape_html(url))
lis = html.xpath('//ol[@class="grid_view"]/li')
# 每個li標(biāo)簽里有每部電影的基本信息
for li in lis:
name = li.xpath('.//div[@class="hd"]/a/span[1]/text()')[0]
director_actor = li.xpath('.//div[@class="bd"]/p/text()')[0].strip()
info = li.xpath('.//div[@class="bd"]/p/text()')[1].strip()
# 按"/"切割成列表
_info = info.split("/")
# 得到 上映時間 上映地區(qū) 電影類型信息 去除兩端多余空格
time_, area, genres = _info[0].strip(), _info[1].strip(), _info[2].strip()
# print(time, area, genres)
rating_score = li.xpath('.//div[@class="star"]/span[2]/text()')[0]
rating_num = li.xpath('.//div[@class="star"]/span[4]/text()')[0]
quote = li.xpath('.//p[@class="quote"]/span/text()')
# 有些電影信息沒有一句話引言 加條件判斷 防止報錯
if len(quote) == 0:
quote = None
else:
quote = quote[0]
sheet.append([rank, name, director_actor, time_, area, genres, rating_score, rating_num, quote])
logging.info([rank, name, director_actor, time_, area, genres, rating_score, rating_num, quote])
rank += 1
if __name__ == '__main__':
rank = 1
for i in range(10):
get_data(i)
wb.save(filename='movie_info1.xlsx')
結(jié)果如下:
六、總結(jié)
-
爬取網(wǎng)頁數(shù)據(jù)用正則表達(dá)式的話,可以直接從網(wǎng)頁源代碼文本中匹配,但出錯率較高,且熟悉正則表達(dá)式的使用也比較難,需要經(jīng)常翻閱文檔。
-
實際爬取數(shù)據(jù)大多基于 HTML 結(jié)構(gòu)的 Web 頁面,網(wǎng)頁節(jié)點(diǎn)較多,各種層級關(guān)系??梢钥紤]使用 Xpath 解析器、BeautifulSoup解析器、PyQuery CSS解析器抽取結(jié)構(gòu)化數(shù)據(jù),使用正則表達(dá)式抽取非結(jié)構(gòu)化數(shù)據(jù)。
-
Xpath:可在 XML 中查找信息;支持 HTML 的查找 ;通過元素和屬性進(jìn)行導(dǎo)航,查找效率很高。在學(xué)習(xí) Selenium 以及 Scrapy 框架中也都會用到。
-
BeautifulSoup:依賴于 lxml 的解析庫,也可以從 HTML 或 XML 文件中提取數(shù)據(jù)。
-
PyQuery:Python仿照 jQuery 嚴(yán)格實現(xiàn),可以直接解析 DOM 節(jié)點(diǎn)的結(jié)構(gòu),并通過 DOM 節(jié)點(diǎn)的一些屬性快速進(jìn)行內(nèi)容提取。
對于爬取網(wǎng)頁結(jié)構(gòu)簡單的 Web 頁面,有些代碼是可以復(fù)用的,如下所示:
from fake_useragent import UserAgent
# 隨機(jī)產(chǎn)生請求頭
ua = UserAgent(verify_ssl=False, path='fake_useragent.json')
def random_ua():
headers = {
"Accept-Encoding": "gzip",
"User-Agent": ua.random
}
return headers
偽裝請求頭,并可以隨機(jī)切換,封裝為函數(shù),便于復(fù)用。
def scrape_html(url):
resp = requests.get(url, headers=random_ua())
# print(resp.status_code, type(resp.status_code))
# print(resp.text)
if resp.status_code == 200:
return resp.text
else:
logging.info('請求網(wǎng)頁失敗')
請求網(wǎng)頁,返回狀態(tài)碼為 200 說明能正常請求,并返回網(wǎng)頁源代碼文本。
最后
我們準(zhǔn)備了一門非常系統(tǒng)的爬蟲課程,除了為你提供一條清晰、無痛的學(xué)習(xí)路徑,我們甄選了最實用的學(xué)習(xí)資源以及龐大的主流爬蟲案例庫。短時間的學(xué)習(xí),你就能夠很好地掌握爬蟲這個技能,獲取你想得到的數(shù)據(jù)。
01 專為0基礎(chǔ)設(shè)置,小白也能輕松學(xué)會
我們把Python的所有知識點(diǎn),都穿插在了漫畫里面。
在Python小課中,你可以通過漫畫的方式學(xué)到知識點(diǎn),難懂的專業(yè)知識瞬間變得有趣易懂。
你就像漫畫的主人公一樣,穿越在劇情中,通關(guān)過坎,不知不覺完成知識的學(xué)習(xí)。
02 無需自己下載安裝包,提供詳細(xì)安裝教程
03 規(guī)劃詳細(xì)學(xué)習(xí)路線,提供學(xué)習(xí)視頻
04 提供實戰(zhàn)資料,更好鞏固知識
05 提供面試資料以及副業(yè)資料,便于更好就業(yè)
文章來源:http://www.zghlxwxcb.cn/news/detail-792185.html
這份完整版的Python全套學(xué)習(xí)資料已經(jīng)上傳CSDN,朋友們?nèi)绻枰部梢話呙柘路絚sdn官方二維碼或者點(diǎn)擊主頁和文章下方的微信卡片獲取領(lǐng)取方式,【保證100%免費(fèi)】文章來源地址http://www.zghlxwxcb.cn/news/detail-792185.html
到了這里,關(guān)于Python 爬蟲:教你四種姿勢解析提取數(shù)據(jù)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!