數據解析
什么是數據解析及作用
概念:就是將一組數據中的局部數據進行提取
作用:來實現聚焦爬蟲
數據解析的通用原理
標簽定位
取文本或者屬性
正則解析
正則回顧
單字符:
. : 除換行以外所有字符
[] :[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 re
#提取出python
key="javapythonc++php"
res = re.findall('python',key)[0] #re.findall('python',key)返回的結果是列表類型的數據
print(res)
#提取出hello world
key="<html><h1>hello world<h1></html>"
re.findall('<h1>(.*)<h1>',key)[0]
#提取170
string = '我喜歡身高為170的女孩'
re.findall('\d+',string)
#提取出http://和https://
key='http://www.baidu.com and https://boob.com'
re.findall('https?://',key)
#提取出hello
key='lalala<hTml>hello</HtMl>hahah' #輸出<hTml>hello</HtMl>
re.findall('<[Hh][Tt][mM][lL]>(.*)</[Hh][Tt][mM][lL]>',key)
#提取出hit.
key='bobo@hit.edu.com'#想要匹配到hit.
re.findall('h.*?\.',key)
#匹配sas和saas
key='saas and sas and saaas'
re.findall('sa{1,2}s',key)
正則爬取
#進行全站數據的爬取:爬取所有頁碼的圖片數據
#需求的實現
#制定一個通用的url模板,不可以被改變
url = 'http://duanziwang.com/category/搞笑圖/%d/'
for page in range(1,4):
new_url = format(url%page)
page_text = requests.get(new_url,headers=headers).text #頁面源碼數據
#新建一個文件夾
dirName = 'imgLibs'
if not os.path.exists(dirName):
os.mkdir(dirName)
#數據解析:每一張圖片的地址
ex = '<article.*?<img src="(.*?)" alt=.*?</article>'
img_src_list = re.findall(ex,page_text,re.S) #爬蟲中使用findall函數必須要使用re.S
for src in img_src_list:
imgName = src.split('/')[-1]
imgPath = dirName+'/'+imgName
urllib.request.urlretrieve(url=src,filename=imgPath)
print(imgName,'下載成功!!!')
bs4
環境的安裝:
pip install bs4
pip install lxml
bs4的解析原理
實例化一個BeautifulSoup的對象,並且將即將被解析的頁面源碼數據加載到該對象中
調用BeautifulSoup對象中的相關屬性和方法進行標簽定位和數據提取
如何實例化BeautifulSoup對象呢?
BeautifulSoup(fp,'lxml'):專門用作於解析本地存儲的html文檔中的數據
BeautifulSoup(page_text,'lxml'):專門用作於將互聯網上請求到的頁面源碼數據進行解析
bs4的基本語法
基礎語法:
soup = BeautifulSoup(page_text,'lxml')
(1)根據標簽名查找
- soup.a 只能找到第一個符合要求的標簽
(2)獲取屬性
- soup.a.attrs 獲取a所有的屬性和屬性值,返回一個字典
- soup.a.attrs['href'] 獲取href屬性
- soup.a['href'] 也可簡寫為這種形式
(3)獲取內容
- soup.a.string 獲取a標簽的直系文本
- soup.a.text 這是屬性,獲取a子類的所有文本
- soup.a.get_text() 這是方法,獲取a標簽子類的所有文本
【注意】如果標簽還有標簽,那么string獲取到的結果為None,而其它兩個,可以獲取文本內容
(4)find:找到第一個符合要求的標簽
- soup.find('a') 找到第一個符合要求的
- soup.find('a', title="xxx") 具有title=a屬性的
- soup.find('a', alt="xxx")
- soup.find('a', class_="xxx")
- soup.find('a', id="xxx")
(5)find_all:找到所有符合要求的標簽
- soup.find_all('a')
- soup.find_all(['a','b']) 找到所有的a和b標簽
- soup.find_all('a', limit=2) 限制前兩個
(6)根據選擇器選擇指定的內容
select:soup.select('#feng')
- 常見的選擇器:標簽選擇器(a)、類選擇器(.)、id選擇器(#)、層級選擇器
- 層級選擇器:
div .dudu #lala .meme .xixi 下面好多級
div > p > a > .lala 只能是下面一級
select就是css選擇器
【注意】select選擇器返回永遠是列表,需要通過索引提取指定的對象
select 和 find 和findall
soup = BeautifulSoup(html, ‘lxml‘)
s = soup.select(‘div .lily‘)#select的寫法和find有區別,select是標簽和class都在一個字符串里,find是兩個字符串,用逗號隔開
f = soup.find(‘div‘,class_ = ‘lily‘) #find只取第一個值,返回的是字符串
fa = soup.find_all(‘div‘,class_ = ‘lily‘)#find——all是全部的值和select一樣,是一個列表
fal = soup.find_all(‘div‘,class_ = ‘lily‘,limit=1)#find——all是全部的值和select一樣,是一個列表,加limit屬性后只返回第一個
print(s)
print(f)
print(fa)
print(fal)
>>>
[<div class="lily" id="ben">大笨蛋</div>, <div class="lily" id="ben">是個大笨蛋嗎?</div>]
<div class="lily" id="ben">大笨蛋</div>
[<div class="lily" id="ben">大笨蛋</div>, <div class="lily" id="ben">個大笨蛋嗎?</div>]
[<div class="lily" id="ben">大笨蛋</div>]
屬性定位:soup.find('tagName',attrName='value'),返回也是單數
find_all:和find用法一致,但是返回值是列表
1. name參數的四種過濾器
soup=Beautifulsoup('page','lxml')
不帶過濾器: print(soup.find_all()) #沒有過濾,查找所有標簽
字符串過濾器: print (soup.find_all()) #字符串過濾器,即標簽名
列表: print(soup.find_(['a','b'])) #找到所有的a標簽和b標簽,任一即可
正則: print(soup.find_all(re.complie('^b'))) #找到所有b開頭的標簽
方法: def has_class_but_no_id(tag):
return tag.has_attr('class') and not tag.has_attr('id')
print(soup.find_all(has_class_but_no_id))
2、按照類名查找,注意關鍵字是class_,class_=value,value可以是五種選擇器之一
print(soup.find_all('a',class_='sister')) #查找類為sister的a標簽
print(soup.find_all('a',class_='sister ssss')) #查找類為sister和sss的a標簽,順序錯誤也匹配不成功
print(soup.find_all(class_=re.compile('^sis'))) #查找類為sister的所有標簽
3、attrs
print(soup.find_all('p',attrs={'class':'story'}))
4、text: 值可以是:字符,列表,True,正則
print(soup.find_all(text='Elsie'))
print(soup.find_all('a',text='Elsie'))
5、limit參數:如果文檔樹很大那么搜索會很慢.如果我們不需要全部結果,可以使用 limit 參數限制返回結果的數量.效果與SQL中的limit關鍵字類似,
當搜索到的結果數量達到 limit 的限制時,就停止搜索返回結果
print(soup.find_all('a',limit=2))
6、recursive:調用tag的 find_all() 方法時,Beautiful Soup會檢索當前tag的所有子孫節點,如果只想搜索tag的直接子節點,可以使用參數 recursive=False .
print(soup.html.find_all('a'))
print(soup.html.find_all('a',recursive=False))
7. find和find_all一樣
爬取三國演義的章節信息和文章內容
分析:
1.先獲取三國演義的主頁面,里面包含了三國演義的文章章節標題,每個文章的章節都是一個a標簽,訪問這個a標簽,就能查看文章的內容
2.發送請求,請求三國演義的主界面
3.在三國演義的主頁面的html源碼中找到章節的標簽位置,定位標簽位置
4.拿到列表數據,循環列表,循環發送章節的內容的請求
import requests
from bs4 import BeautifulSoup # 導入BeautifulSoup
url = 'http://www.shicimingju.com/book/sanguoyanyi.html' #
headers={
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36'
}
sg_list = requests.get(url=url,headers=headers).text
soup = BeautifulSoup(sg_list,'lxml')
content = soup.select('.book-mulu > ul > li > a') #章節的標簽
f = open('sanguo.txt','w',encoding='utf-8')
for i in content:
new_url = 'http://www.shicimingju.com'+i['href'] #拼接標簽的訪問路徑
title = i.string
detail = requests.get(url=new_url,headers=headers).text #循環發送對文章內容的請求
soup = BeautifulSoup(detail,'lxml')
new_detail = soup.find('div',class_="chapter_content").text
f.write(new_detail)
print(title+'爬取成功')
f.close()
xpath
xpath安裝及基本語法
環境的安裝:pip install lxml
xpath的解析原理
實例化一個etree類型的對象,且將頁面源碼數據加載到該對象中
需要調用該對象的xpath方法結合着不同形式的xpath表達式進行標簽定位和數據提取
etree對象的實例化
etree.parse(fileNane) #本地的html文件
etree.HTML(page_text) #聲明了一段HTML文本,調用HTML類進行初始化,構造了一個XPath解析對象
xpath方法返回的永遠是一個列表
標簽定位
xpath表達式中最最側的/表示的含義是說,當前定位的標簽必須從根節點開始進行定位
xpath表達式中最左側的//表示可以從任意位置進行標簽定位
xpath表達式中非最左側的//表示的是多個層級的意思
xpath表達式中非最左側的/表示的是一個層級的意思
屬性定位
//標簽名[@arrtName='value']
循環中標簽定位: ./表示當前標簽
索引定位://標簽名/li[3] #第三個li標簽
提取數據
取文本:
/text():取直系的文本內容
//text():取所有的文本內容,循環中不能再用索引,例如文本中有br標簽分割
取屬性:
tag/@attrName
舉例:
from lxml import etree
tree = etree.parse('./test.html')
tree.xpath('/html/head/meta')[0] #絕對路徑
tree.xpath('//meta')[0] #相對路徑,將整個頁面源碼中所有的meta進行定位
tree.xpath('/html//meta')[0]
#屬性定位
tree.xpath('//div[@class="song"]')
#索引定位
tree.xpath('//div[@class="tang"]/ul/li[3]') #該索引是從1開始
tree.xpath('//div[@class="tang"]//li[3]') #該索引是從1開始
#取文本
tree.xpath('//p[1]/text()')
tree.xpath('//div[@class="song"]//text()')
#取屬性
tree.xpath('//a[@id="feng"]/@href')
爬取boss的招聘信息
爬取的內容:崗位名稱,公司名稱,薪資,崗位描述
url = 'https://www.zhipin.com/job_detail/? query=python%E7%88%AC%E8%99%AB&city=101010100&industry=&position=' # python爬蟲崗位的url
headers={
'cookie': '_uab_collina=157338945037017905889165; __c=1573389450; __g=-; __l=l=%2Fwww.zhipin.com%2Fweb%2Fcommon%2Fsecurity-check.html%3Fseed%3Dl%252FiZaxWImFKXsBkSlPZFk9r1hTxzYO%252BbuuzP3sRZC3A%253D%26name%3Dcb43d3e3%26ts%3D1573389319454%26callbackUrl%3D%252Fjob_detail%252F%253Fquery%253Dpython%2525E7%252588%2525AC%2525E8%252599%2525AB%2526city%253D101010100%2526industry%253D%2526position%253D%26srcReferer%3D&r=&friend_source=0&friend_source=0; Hm_lvt_194df3105ad7148dcf2b98a91b5e727a=1573389451,1573390721; Hm_lpvt_194df3105ad7148dcf2b98a91b5e727a=1573392780; __zp_stoken__=48121bb20qltoY5SdLcZPo5yn7gLjgHQMQN16Gyq4%2B26dpVXYTOQYI8oCaMpwStY3B5JC%2Fg5rjYSnOw4oEuk5fSh4A%3D%3D; __a=96130844.1573389450..1573389450.7.1.7.7',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36'
}
#cookies反爬機制,cookies有可能隨時變化
page_text = requests.get(url,headers=headers).text
#數據解析
tree = etree.HTML(page_text)
li_list = tree.xpath('//div[@class="job-list"]/ul/li')
for li in li_list:
# 需要將li表示的局部頁面源碼數據中的相關數據進行提取
# 如果xpath表達式被作用在了循環中,表達式要以./或者.//開頭
detail_url = 'https://www.zhipin.com'+li.xpath('.//div[@class="info-primary"]/h3/a/@href')[0]
job_title = li.xpath('.//div[@class="info-primary"]/h3/a/div/text()')[0]
salary = li.xpath('.//div[@class="info-primary"]/h3/a/span/text()')[0]
company = li.xpath('.//div[@class="info-company"]/div/h3/a/text()')[0]
#對詳情頁的url發請求解析出崗位職責
detail_page_text = requests.get(detail_url,headers=headers).text
tree = etree.HTML(detail_page_text)
job_desc = tree.xpath('//div[@class="text"]//text()')
job_desc = ''.join(job_desc)#job_desc獲取列表數據,通過join變成字符串形式的數據
print(job_title,salary,company,job_desc)
爬取糗事百科的段子內容和作者名稱
headers = {
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.100 Safari/537.36',
}
url = 'https://www.qiushibaike.com/text/page/4/'
page_text = requests.get(url,headers=headers).text
tree = etree.HTML(page_text)
div_list = tree.xpath('//div[@id="content-left"]/div')
for div in div_list:
author = div.xpath('./div[1]/a[2]/h2/text() | ./div[1]/span[2]/h2/text()')[0]
# 糗事百科中有作者和段子內容,作者分為實名用戶和匿名用戶,但通過對糗事百科的源碼,當是匿名用戶的時候,文本內容就取不到,所以返回None,但是爬取到的內容也就是None,不是想要的結果,解決: ./div[1]/a[2]/h2/text()取實名用戶,./div[1]/span[2]/h2/text()取匿名用戶
content = div.xpath('.//div[@class="content"]/span//text()')
content = ''.join(content)
print(author,content)
爬取糗事百科笑話的標題和內容
http://www.lovehhy.net/Joke/Detail
from lxml import etree
import requests
url = 'http://www.lovehhy.net/Joke/Detail/QSBK/'
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36',
'Cookie':'bdshare_firstime=1573442373487; ASP.NET_SessionId=0yoewt3nnhet3apass0u4hj5; ASPSESSIONIDQADBDDCA=AAKLABFACJHJNHHCMCKEJGJB; __51cke__=; Hm_lvt_03da7ad267ef3d61ce133d6c12f67140=1573442375,1573478536; ASPSESSIONIDSACCBCCA=BCOLDPEALOKMHFJJMHODNHGB; Hm_lpvt_lovehhy=1573479577; Hm_lvt_lovehhy=1573479577; Hm_lpvt_03da7ad267ef3d61ce133d6c12f67140=1573479707; __tins__2343955=%7B%22sid%22%3A%201573478536404%2C%20%22vd%22%3A%2011%2C%20%22expires%22%3A%201573481507039%7D; __51laig__=11'
}
joke_text = requests.get(url=url,headers=headers).text
tree = etree.HTML(joke_text)
url_text = tree.xpath('//*[@id="footzoon"]/h3/a/@href')
url_text
for i in url_text:
qiu_url = 'http://www.lovehhy.net' + i
content_text = requests.get(url=qiu_url,headers=headers).text
tree = etree.HTML(content_text)
title = tree.xpath('//*[@id="read"]/h1/text()')[0]
content = tree.xpath('//*[@id="fontzoom"]/text()')[0]
print(title,content)
