一、啥是數據解析 在上一篇關於爬蟲的博客里,我提到過,整個爬蟲分為四個部分,上一篇博客已經完成了前兩步,也就是我說的最難的地方,接下來這一步數據解析不是很難,但就是很煩人,但只要你有耐心,一步一步查找、排除就會提取出目標信息,這一步就相當於從接收到的龐大數據中提取出真正想要、有意義的信息,所以對於爬 ...
一、啥是數據解析
在上一篇關於爬蟲的博客里,我提到過,整個爬蟲分為四個部分,上一篇博客已經完成了前兩步,也就是我說的最難的地方,接下來這一步數據解析不是很難,但就是很煩人,但只要你有耐心,一步一步查找、排除就會提取出目標信息,這一步就相當於從接收到的龐大數據中提取出真正想要、有意義的信息,所以對於爬蟲來說,應該是很重要的。
數據解析有三種方式,一是通過正則表達式,在python中就是利用re模塊;二是xpath;三是利用BeautifulSoup。
二、正則表達式
之前我們在學模塊的時候講過正則表達式,在這就不細說,獻上經常用到的
單字元: . : 除換行以外所有字元 [] :[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(正則表達式, 替換內容, 字元串)
三、xpath
1,常用表達式
屬性定位: #找到class屬性值為song的div標簽 //div[@class="song"] 層級&索引定位: #找到class屬性值為tang的div的直系子標簽ul下的第二個子標簽li下的直系子標簽a //div[@class="tang"]/ul/li[2]/a 邏輯運算: #找到href屬性值為空且class屬性值為du的a標簽 //a[@href="" and @class="du"] 模糊匹配: //div[contains(@class, "ng")] //div[starts-with(@class, "ta")] 取文本: # /表示獲取某個標簽下的文本內容 # //表示獲取某個標簽下的文本內容和所有子標簽下的文本內容 //div[@class="song"]/p[1]/text() //div[@class="tang"]//text() 取屬性: //div[@class="tang"]//li[2]/a/@href
我們在使用xpath時,想要把字元串轉化為etree對象:
tree=etree.parse(文件名)#這種是把一個本地文件轉化成rtree對象
tree=etree.HTML(html標簽字元串)
tree.xpath(xpath表達式) #這樣就可以通過找到某個標簽,取出標簽的某個屬性就得到想要的結果
2,示例一,爬取糗事百科圖片,保存在本地
import requests
from lxml import etree
#這是請求的路徑
url='https://www.qiushibaike.com/pic/' #這是偽造的瀏覽器UA
headers={ 'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.119 Safari/537.36' }
#content拿到的是頁面代碼 content=requests.get(url=url,headers=headers).text
#把這個頁面字元串代碼轉換成etree對象 tree=etree.HTML(content)
#這是拿到所有class=‘thumb’的div標簽下的img標簽的src屬性,返回的是一個列表 img_src_list=tree.xpath('//div[@class="thumb"]//img/@src')
#迴圈每個src,然後再去訪問,拿到圖片的位元組數據,存放於JPG文件,就得到每張圖片了 for img_src in img_src_list: c1=requests.get(url='https:'+img_src,headers=headers).content with open('%s.jpg'%img_src[:5],'wb') as f: f.write(c1)
3,示例二,爬取煎蛋網的圖片
這個就不是那麼簡單了,可以說是及其的難,我們用瀏覽器去訪問一下煎蛋網,查看一下每張圖片的src。
在這個元素的頁面上,也就是載入完畢後的HTML文件,上面可以看到img的src屬性,不用猜,這個肯定是圖片的地址,很是興奮,急急忙忙的寫程式,訪問頁面,拿去img的src值,然後再發起請求拿到圖片數據,保存下來,運行程式出錯了,不是預期的結果。在這,給大家分享一個反爬機制,對於圖片的src屬性並不是直接寫在html頁面上的,而是在載入頁面時用js得到img的src屬性,然後賦值過去,其實我們可以點開network,查看response,這個response才是真正返回的HTML文件內容,也就是接收的內容。如下圖:
從response來看,它的所有圖片的src都是一樣的,說明並不是圖片真正的輸入窗路徑,後面跟了一個span標簽,class為img-hash,文本內容為一大段字元,可以猜出這是一個hash值,這個值就是img的src加密後的hash值,所以在載入頁面時,通過js把加密的字元解開就是img的src屬性,然後再賦給src(別問我是咋知道,我看別人這樣寫的,但確實是對的),這種通過js來動態載入的頁面是一種反爬機制,而且是一種讓人很頭疼的反爬機制。
現在我們想要拿到他的src,就需要我們從返回的html文件中取出每個img-hash值,然後解密,得到真正的src,然後再對src發起請求。
import requests from lxml import etree headers={ 'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.119 Safari/537.36' } import base64 url='http://jandan.net/ooxx' content=requests.get(url=url,headers=headers).text tree=etree.HTML(content) hash_list=tree.xpath('//span[@class="img-hash"]/text()') #這是拿到了所有的img-hsah值,存放在一個列表中 for i in hash_list: ur=base64.b64decode(i).decode() #這裡用base64解密(不要問我為啥用這個解密,你咋知道的。大佬說,在js代碼發現有base64和md5的字樣,然而md5是不可逆的,所以就是base64了) con=requests.get(url='http:'+ur,headers=headers).content with open('%s.jpg'%i,'wb') as f: f.write(con)
四、BeautifulSoup
1,方法
from bs4 import BeautifulSoup soup = BeautifulSoup(open('本地文件'), 'lxml') #這是把一個本地文件轉換成BeautifulSoup對象 soup = BeautifulSoup('字元串類型或者位元組類型', 'lxml')#這是把HTML字元串轉換成BeautifulSoup對象 基礎鞏固: (1)根據標簽名查找 - soup.a 只能找到第一個a標簽,其他標簽一樣 (2)獲取屬性 - soup.a.attrs 獲取第一個a標簽所有的屬性和屬性值,返回一個字典 - soup.a.attrs['href'] 獲取href屬性 - soup.a['href'] 也可簡寫為這種形式 (3)獲取內容 - soup.a.string - soup.a.text - soup.a.get_text() 【註意】如果標簽還有標簽,那麼string獲取到的結果為None,而其它兩個,可以獲取文本內容 (4)find:找到第一個符合要求的標簽 - soup.find('a') - soup.find('a', title="xxx") - soup.find('a', alt="xxx") - soup.find('a', class_="xxx") #按類查找,得在把class寫成class_ - 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)根據選擇器選擇指定的內容 #選擇器的規則和css一模一樣, select:soup.select('#feng') - 常見的選擇器:標簽選擇器(a)、類選擇器(.)、id選擇器(#)、層級選擇器 - 層級選擇器: div .dudu #lala .meme .xixi 下麵好多級 div > p > a > .lala 只能是下麵一級 【註意】select選擇器返回永遠是列表,需要通過下標提取指定的對象
2,實例一,爬取抽屜網的新聞標題和連接
from bs4 import BeautifulSoup import requests url='https://dig.chouti.com/' headers={ 'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.119 Safari/537.36' } con=requests.get(url=url,headers=headers).text
#這是實例化一個BeautifulSoup對象,對象就可以使用find、find_all等方法 soup=BeautifulSoup(con,'lxml') a_list=soup.find_all('a',class_="show-content color-chag")#這是拿到了很多的a標簽, data_list=[] for a in a_list: dic={} dic['url']=a['href'] dic['content']=a.text.strip() data_list.append(dic) print(data_list)
3,實例二,爬取58同城的房源信息
from bs4 import BeautifulSoup import requests import xlwt import re headers={ 'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.119 Safari/537.36' }
#這是創建一個Excel,並創建一個sheet表,設置屬性 workbook = xlwt.Workbook(encoding='ascii') worksheet = workbook.add_sheet('My Worksheet') worksheet.col(0).width = 14000 worksheet.col(5).width = 24000 #發送請求 url='https://sz.58.com/ershoufang/?utm_source=market&spm=u-2d2yxv86y3v43nkddh1.BDPCPZ_BT&PGTID=0d30000c-0000-4591-0324-370565eccba8&ClickID=1' res=requests.get(url=url,headers=headers) con=res.text
#實例化一個對象 soup=BeautifulSoup(con,'lxml') ss=soup.find('ul',class_='house-list-wrap') li_list=ss.find_all('li') #這是拿到了所有的li標簽 patter=re.compile(r'\s',re.S)
#這是迴圈每個li標簽,這裡拿到的每個li標簽還是一個BeautifulSoup對象,一樣擁有find、find_all等方法,對每個li標簽處理拿到每個房源的各種信息,然後寫入Excel中 for num in range(len(li_list)): worksheet.write(num, 0, label=li_list[num].find('a',tongji_label="listclick").text.strip()) p1=li_list[num].find_all('p')[0] span_list=p1.find_all('span') worksheet.write(num, 1, label=patter.sub('',span_list[0].text)) worksheet.write(num, 2, label=patter.sub('',span_list[1].text)) worksheet.write(num, 3, label=patter.sub('',span_list[2].text)) worksheet.write(num, 4, label=patter.sub('',span_list[3].text)) worksheet.write(num,5, label=patter.sub('',li_list[num].find('div',class_='jjrinfo').text)) worksheet.write(num, 6, label=li_list[num].find('p',class_='sum').text) worksheet.write(num, 7, label=li_list[num].find('p',class_='unit').text) workbook.save('myWorkbook.xls')
4,實例三,爬取github
github是需要登錄驗證的,所以我們照我上一篇講的模擬登錄的步驟,先用瀏覽器輸入一組錯誤信息,點擊登錄,找的登錄發送的路徑和數據結構,
明顯發現這就是登錄請求的路徑,數據結構拿到了,再去拿到請求的路徑
這下就可以發送請求,我最先訪問的是login頁面,得到cookie,帶這個cookie和data數據,往登錄的路徑發送請求,但不得行。於是乎回來看了一看,要求的數據結構,其中有個叫token的東西,怎麼那麼熟悉,這個不是那個隨機值CSRF-token,我就再去看了一下HTML頁面,
確實是基於form表單發送請求的CSRF-token,這個東西是一個隨機值,所以我的程式得想去訪問login頁面,拿到登陸頁面,取得這個token值,放在data數據里,我之前程式的其他部分就不用變了,於是乎就成功了。
import requests import re url='https://github.com/login' url1='https://github.com/session' headers={ 'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.119 Safari/537.36', } session=requests.session() res1=session.get(url=url,headers=headers) token=re.findall(r'name="authenticity_token".*?value="(?P<name>.*?)"',res1.text,re.S)[0] #這就是用BeautifulSoup取得token值 data={ 'commit': 'Sign in', 'utf8': '✓', 'authenticity_token': token, 'login': 'xxxxxx', 'password':'xxxxx' } session.post(url=url1,data=data,headers=headers) url2='https://github.com/' res=session.get(url=url2,headers=headers) with open('github1.html','wb') as f: f.write(res.content)