一, 引入
回顧requests實現數據爬取的流程:
- 指定url
- 基於requests模塊發起請求
- 獲取響應對象中的數據
- 進行持久化存儲
其實,在上述流程中還需要較為重要的一步,就是在持久化存儲之前需要進行指定數據解析。因為大多數情況下的需求,我們都會指定去使用聚焦爬蟲,也就是爬取頁面中指定部分的數據值,而不是整個頁面的數據。數據爬取的流程可以修改為:
- 指定url
- 基於requests模塊發起請求
- 獲取響應中的數據
- 數據解析
- 進行持久化存儲
實現方式:
- 正則
- bs4
- xpath
- pyquery
數據解析的通用原理是什么?
- 標簽的定位
- 數據的提取
頁面中的相關的字符串的數據都存儲在哪里呢?
- 標簽中間
- 標簽的屬性中
二, 正則解析
常用正則表達式回顧:
單字符:
. : 除換行以外所有字符
[] :[aoe] [a-w] 匹配集合中任意一個字符
\d :數字 [0-9]
\D : 非數字
\w :數字、字母、下划線、中文
\W : 非\w
\s :所有的空白字符包,括空格、制表符、換頁符等等。等價於 [ \f\n\r\t\v]。
\S : 非空白
數量修飾:
* : 任意多次 >=0
+ : 至少1次 >=1
? : 可有可無 0次或者1次
{m} :固定m次 hello{3,}
{m,} :至少m次
{m,n} :m-n次
邊界:
$ : 以某某結尾
^ : 以某某開頭
分組:
(ab)
貪婪模式: .*
非貪婪(惰性)模式: .*?
re.I : 忽略大小寫
re.M :多行匹配
re.S :單行匹配
re.sub(正則表達式, 替換內容, 字符串)
練習:
將煎蛋網中的圖片數據進行爬取且存儲在本地
import requests
import re
import os
from urllib import request
file_path = './煎蛋網/'
if not os.path.exists(file_path):
os.mkdir(file_path)
url = 'http://jandan.net/pic/MjAxOTEwMDktNjY=#comments'
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36'
}
page_text = requests.get(url, headers=headers).text
ex = '<div class="text">.*?<img src="(.*?)" referrerPolicy.*?</div>'
src_list = re.findall(ex, page_text, re.S)
for src in src_list:
if 'org_src' in src:
src = re.findall('org_src="(.*?)" onload', src)[0]
img_url = 'http:' + src
img_path = file_path + src.split('/')[-1]
request.urlretrieve(img_url, img_path)
print(src.split('/')[-1], '爬取成功~~')
三, Xpath解析
- 環境的安裝
- pip install lxml
- 解析原理
- 實例化一個etree的對象,且把即將被解析的頁面源碼數據加載到該對象中
- 調用etree對象中的xpath方法結合着不同形式的xpath表達式進行標簽定位和數據提取
- etree對象的實例化
- etree.parse('fileName')
- etree.HTML(page_text)
- 標簽定位
- 最左側的/:一定要從根標簽開始進行標簽定位
- 非最左側的/:表示一個層級
- 最左側的//:可以從任意位置進行指定標簽的定位
- 非最左側的//:表示多個層級
- 屬性定位://tagName[@attrName="value"]
- 索引定位://tagName[@attrName="value"]/li[2],索引是從1開始
- 邏輯運算:
- 找到href屬性值為空且class屬性值為du的a標簽
- //a[@href="" and @class="du"]
- 模糊匹配:
- //div[contains(@class, "ng")]
- //div[starts-with(@class, "ta")]
- 取文本
- /text():直系的文本內容
- //text():所有的文本內容
- 取屬性
- /@attrName
練習:
需求:爬取虎牙主播名稱,熱度和標題
from lxml import etree
url = 'https://www.huya.com/g/xingxiu'
page_text = requests.get(url, headers=headers).text
tree = etree.HTML(page_text)
li_list = tree.xpath('//ul[@id="js-live-list"]/li')
for li in li_list:
title = li.xpath('./a[2]/text()')[0]
avatar = li.xpath('./span/span[1]/i/text()')[0]
hot = li.xpath('./span/span[2]/i[2]/text()')[0]
print(title, avatar, hot)
爬取http://pic.netbian.com/4kmeinv/中前五頁的圖片數據
- 中文亂碼的處理
- 多頁碼數據的爬取
file_path = './妹子圖/'
if not os.path.exists(file_path):
os.mkdir(file_path)
url = 'http://pic.netbian.com/4kmeinv/index_{}.html'
for page in range(1, 6):
if page == 1:
new_url = 'http://pic.netbian.com/4kmeinv/'
else:
new_url = url.format(page)
page_text = requests.get(new_url, headers=headers).text
tree = etree.HTML(page_text)
li_list = tree.xpath('//div[@class="slist"]/ul/li')
for li in li_list:
title = li.xpath('./a/b/text()')[0].encode('iso-8859-1').decode('gbk')
src = 'http://pic.netbian.com' + li.xpath('./a/img/@src')[0]
img_path = file_path + title + '.' + src.split('.')[-1]
request.urlretrieve(src, img_path)
print('第{}頁爬取完畢'.format(page))
爬取全國城市的名稱
url = 'https://www.aqistudy.cn/historydata/'
page_text = requests.get(url, headers=headers).text
# soup = BeautifulSoup(page_text, 'lxml')
# hot_cities = soup.select('.hot li a')
# other_cities = soup.select('.all li a')
# cities = hot_cities + other_cities
# for city in cities:
# print(city.text)
tree = etree.HTML(page_text)
# | 表示 or
cities = tree.xpath('//div[@class="hot"]//a/text() | //div[@class="all"]//a/text()')
for city in cities:
print(city)
四, BeautifulSoup解析
-
環境的安裝:
- pip install bs4
- pip install lxml
-
bs4的解析原理:
- 實例化一個BeautifulSoup的一個對象,把即將被解析的頁面源碼數據加載到該對象中
- 需要調用BeautifulSoup對象中的相關的方法和屬性進行標簽定位和數據的提取
-
BeautifulSoup的實例化
- BeautifulSoup(fp,'lxml'):將本地存儲的html文檔中的頁面源碼數據加載到該對象中
- BeautifulSoup(page_text,'lxml'):將從互聯網中請求到的頁面源碼數據加載到該對象中
-
標簽的定位
- soup.tagName:只可以定位到第一個tagName標簽
- 屬性定位:soup.find('tagName',attrName='value'),只可以定位到符合要求的第一個標簽
- 特別的: class屬性 class_='value'
- findAll:返回值是一個列表。可以定位到符合要求的所有標簽
- 選擇器定位:soup.select('選擇器')
- 選擇器:id,class,tag,層級選擇器(大於號表示一個層級,空格表示多個層級)
-
取文本
- text:將標簽中所有的文本取出
- string:將標簽中直系的文本取出
-
取屬性
- tag['attrName']
練習:
需求:使用bs4解析紅樓夢小說的標題和內容,存儲到本地
url = 'http://www.shicimingju.com/book/hongloumeng.html'
page_text = requests.get(url, headers=headers).text
soup = BeautifulSoup(page_text, 'lxml')
a_list = soup.select('.book-mulu a')
f1 = open('紅樓夢.text', 'w', encoding='utf-8')
for a in a_list:
title = a.text
book_url = 'http://www.shicimingju.com' + a['href']
book_text = requests.get(book_url, headers=headers).text
book_soup = BeautifulSoup(book_text, 'lxml')
book_data = book_soup.select('.chapter_content')[0].text
f1.write(title + '\n' + book_data + '\n')
print(title, '爬取成功~')
f1.close()
五, pyquery解析
和jQuery高度相似,容易上手,但是效率和正確率不太好.