1. 程式人生 > >爬蟲-三種數據解析方式

爬蟲-三種數據解析方式

muc mozilla 永遠 self bin pytho 函數 三方庫 china

引入

回顧requests實現數據爬取的流程

  1. 指定url
  2. 基於requests模塊發起請求
  3. 獲取響應對象中的數據
  4. 進行持久化存儲

其實,在上述流程中還需要較為重要的一步,就是在持久化存儲之前需要進行指定數據解析。因為大多數情況下的需求,我們都會指定去使用聚焦爬蟲,也就是爬取頁面中指定部分的數據值,而不是整個頁面的數據。因此,本次課程中會給大家詳細介紹講解三種聚焦爬蟲中的數據解析方式。至此,我們的數據爬取的流程可以修改為:

  1. 指定url
  2. 基於requests模塊發起請求
  3. 獲取響應中的數據
  4. 數據解析
  5. 進行持久化存儲

今日概要

  • 正則解析
  • xpath解析
  • bs4解析

知識點回顧

  • requests模塊的使用流程
  • requests模塊請求方法參數的作用
  • 抓包工具抓取ajax的數據包

一.正解解析

  • 常用正則表達式回顧:
   單字符:
        . : 除換行以外所有字符
        [] :[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"
re.findall(‘python‘,key)[0]
#####################################################################
#提取出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=[email protected]#想要匹配到hit.
re.findall(‘h.*?\.‘,key)
#####################################################################
#匹配sas和saas
key=‘saas and sas and saaas‘
re.findall(‘sa{1,2}s‘,key)
#####################################################################
#匹配出i開頭的行
string = ‘‘‘fall in love with you
i love you very much
i love she
i love her‘‘‘

re.findall(‘^.*‘,string,re.M)
#####################################################################
#匹配全部行
string1 = """<div>靜夜思
窗前明月光
疑是地上霜
舉頭望明月
低頭思故鄉
</div>"""

re.findall(‘.*‘,string1,re.S)
  • 項目需求:爬取糗事百科指定頁面的糗圖,並將其保存到指定文件夾中
    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    import requests
    import re
    import os
    if __name__ == "__main__":
         url = ‘https://www.qiushibaike.com/pic/%s/‘
         headers={
             ‘User-Agent‘: ‘Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36‘,
         }
         #指定起始也結束頁碼
         page_start = int(input(‘enter start page:‘))
         page_end = int(input(‘enter end page:‘))
    
         #創建文件夾
         if not os.path.exists(‘images‘):
             os.mkdir(‘images‘)
         #循環解析且下載指定頁碼中的圖片數據
         for page in range(page_start,page_end+1):
             print(‘正在下載第%d頁圖片‘%page)
             new_url = format(url % page)
             response = requests.get(url=new_url,headers=headers)
    
             #解析response中的圖片鏈接
             e = ‘<div class="thumb">.*?<img src="(.*?)".*?>.*?</div>‘
             pa = re.compile(e,re.S)
             image_urls = pa.findall(response.text)
              #循環下載該頁碼下所有的圖片數據
             for image_url in image_urls:
                 image_url = ‘https:‘ + image_url
                 image_name = image_url.split(‘/‘)[-1]
                 image_path = ‘images/‘+image_name
    
                 image_data = requests.get(url=image_url,headers=headers).content
                 with open(image_path,‘wb‘) as fp:
                     fp.write(image_data)

二.Xpath解析

  • 測試頁面數據
<html lang="en">
<head>
	<meta charset="UTF-8" />
	<title>測試bs4</title>
</head>
<body>
	<div>
		<p>百裏守約</p>
	</div>
	<div class="song">
		<p>李清照</p>
		<p>王安石</p>
		<p>蘇軾</p>
		<p>柳宗元</p>
		<a href="http://www.song.com/" title="趙匡胤" target="_self">
			<span>this is span</span>
		宋朝是最強大的王朝,不是軍隊的強大,而是經濟很強大,國民都很有錢</a>
		<a href="" class="du">總為浮雲能蔽日,長安不見使人愁</a>
		<img src="http://www.baidu.com/meinv.jpg" alt="" />
	</div>
	<div class="tang">
		<ul>
			<li><a href="http://www.baidu.com" title="qing">清明時節雨紛紛,路上行人欲斷魂,借問酒家何處有,牧童遙指杏花村</a></li>
			<li><a href="http://www.163.com" title="qin">秦時明月漢時關,萬裏長征人未還,但使龍城飛將在,不教胡馬度陰山</a></li>
			<li><a href="http://www.126.com" alt="qi">岐王宅裏尋常見,崔九堂前幾度聞,正是江南好風景,落花時節又逢君</a></li>
			<li><a href="http://www.sina.com" class="du">杜甫</a></li>
			<li><a href="http://www.dudu.com" class="du">杜牧</a></li>
			<li><b>杜小月</b></li>
			<li><i>度蜜月</i></li>
			<li><a href="http://www.haha.com" id="feng">鳳凰臺上鳳凰遊,鳳去臺空江自流,吳宮花草埋幽徑,晉代衣冠成古丘</a></li>
		</ul>
	</div>
</body>
</html>
  • 常用xpath表達式回顧
屬性定位:
    #找到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表達式進行數據解析:
1.下載:pip install lxml
2.導包:from lxml import etree

3.將html文檔或者xml文檔轉換成一個etree對象,然後調用對象中的方法查找指定的節點

  2.1 本地文件:tree = etree.parse(文件名)
                tree.xpath("xpath表達式")

  2.2 網絡數據:tree = etree.HTML(網頁內容字符串)
                tree.xpath("xpath表達式")
  • 安裝xpath插件在瀏覽器中對xpath表達式進行驗證:可以在插件中直接執行xpath表達式
    • 將xpath插件拖動到谷歌瀏覽器拓展程序(更多工具)中,安裝成功

    • 啟動和關閉插件 ctrl + shift + x

  • 項目需求:獲取好段子中段子的內容和作者 http://www.haoduanzi.com

    from lxml import etree
    import requests
    
    url=‘http://www.haoduanzi.com/category-10_2.html‘
    headers = {
            ‘User-Agent‘: ‘Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36‘,
        }
    url_content=requests.get(url,headers=headers).text
    #使用xpath對url_conten進行解析
    #使用xpath解析從網絡上獲取的數據
    tree=etree.HTML(url_content)
    #解析獲取當頁所有段子的標題
    title_list=tree.xpath(‘//div[@class="log cate10 auth1"]/h3/a/text()‘)
    
    ele_div_list=tree.xpath(‘//div[@class="log cate10 auth1"]‘)
    
    text_list=[] #最終會存儲12個段子的文本內容
    for ele in ele_div_list:
        #段子的文本內容(是存放在list列表中)
        text_list=ele.xpath(‘./div[@class="cont"]//text()‘)
        #list列表中的文本內容全部提取到一個字符串中
        text_str=str(text_list)
        #字符串形式的文本內容防止到all_text列表中
        text_list.append(text_str)
    print(title_list)
    print(text_list)

【重點】下載煎蛋網中的圖片數據:http://jandan.net/ooxx

import requests
from lxml import etree
from fake_useragent import UserAgent
import base64
import urllib.request
url = ‘http://jandan.net/ooxx‘
ua = UserAgent(verify_ssl=False,use_cache_server=False).random
headers = {
    ‘User-Agent‘:ua
}
page_text = requests.get(url=url,headers=headers).text

#查看頁面源碼:發現所有圖片的src值都是一樣的。
#簡單觀察會發現每張圖片加載都是通過jandan_load_img(this)這個js函數實現的。
#在該函數後面還有一個class值為img-hash的標簽,裏面存儲的是一組hash值,該值就是加密後的img地址
#加密就是通過js函數實現的,所以分析js函數,獲知加密方式,然後進行解密。
#通過抓包工具抓取起始url的數據包,在數據包中全局搜索js函數名(jandan_load_img),然後分析該函數實現加密的方式。
#在該js函數中發現有一個方法調用,該方法就是加密方式,對該方法進行搜索
#搜索到的方法中會發現base64和md5等字樣,md5是不可逆的所以優先考慮使用base64解密
#print(page_text)

tree = etree.HTML(page_text)
#在抓包工具的數據包響應對象對應的頁面中進行xpath的編寫,而不是在瀏覽器頁面中。
#獲取了加密的圖片url數據
imgCode_list = tree.xpath(‘//span[@class="img-hash"]/text()‘)
imgUrl_list = []
for url in imgCode_list:
    #base64.b64decode(url)為byte類型,需要轉成str
    img_url = ‘http:‘+base64.b64decode(url).decode()
    imgUrl_list.append(img_url)

for url in imgUrl_list:
    filePath = url.split(‘/‘)[-1]
    urllib.request.urlretrieve(url=url,filename=filePath)
    print(filePath+‘下載成功‘)

【重點】

- 問題:往往在進行大量請求發送的時候,經常會報出這一樣的一個錯誤:HTTPConnectionPool(host:XX)Max retries exceeded with url。

- 原因:

1.每次數據傳輸前客戶端要和服務器建立TCP連接,為節省傳輸消耗,默認為keep-alive,即連接一次,傳輸多次。然而如果連接遲遲不斷開的話,則連接池滿後則無法產生新的鏈接對象,導致請求無法發送。

2.ip被封

3.請求頻率太頻繁

- 解決:如果下列解決未生效,則可以嘗試再次執行程序

1.設置請求頭中的Connection的值為close,表示每次請求成功後斷開連接

2.更換請求ip

3.每次請求之間使用sleep進行等待間隔

import requests
from fake_useragent import UserAgent
from lxml import etree
import random
import time

url = ‘http://sc.chinaz.com/jianli/free_%d.html‘
ua = UserAgent(verify_ssl=False,use_cache_server=False).random
headers = {
    ‘User-Agent‘:ua,
    #保證http請求成功後,立即斷開連接,以解決HTTPConnectionPool(host:XX)Max retries exceeded with url的問題
    ‘Connection‘: ‘close‘, #該行不寫,則會報錯
}
#proxy_list = [‘101.255.56.201:36501‘,‘39.137.69.10:8080‘,‘195.29.106.178:58292‘,‘120.76.77.152:9999‘,‘178.75.1.111:50411‘,‘78.156.225.170:41258‘,‘193.192.177.196:56480‘]
for pageNum in range(1,3):
    get_url = format(url%pageNum)
    if pageNum==1:
        get_url = ‘http://sc.chinaz.com/jianli/free.html‘
    response = requests.get(url=get_url,headers=headers)
    response.encoding = ‘utf-8‘#處理中文亂碼問題
    page_text = response.text
    tree = etree.HTML(page_text)
    div_list = tree.xpath(‘//div[@id="container"]/div‘)
    for div in div_list:
        second_url = div.xpath(‘./a/@href‘)[0]
        name = div.xpath(‘./p/a/text()‘)[0]+‘.rar‘
        second_page_text = requests.get(url=second_url,headers=headers).text
        second_tree = etree.HTML(second_page_text)
        download_url_list = second_tree.xpath(‘//div[@class="clearfix mt20 downlist"]/ul/li/a/@href‘)
        download_url = random.choice(download_url_list)
        data = requests.get(url=download_url,headers=headers).content
        with open(name,‘wb‘) as fp:
            fp.write(data)
            print(‘下載完畢===>‘+name)

#解決HTTPConnectionPool(host:XX)Max retries exceeded with url的問題:
        #time.sleep(3) #延長請求時間,模擬瀏覽器,否則請求頻率太快會請求失敗
        #使用代理池
        #設置請求頭信息中的Connection為close

三.BeautifulSoup解析

  • 環境安裝
- 需要將pip源設置為國內源,阿裏源、豆瓣源、網易源等
   - windows
    (1)打開文件資源管理器(文件夾地址欄中)
    (2)地址欄上面輸入 %appdata%
    (3)在這裏面新建一個文件夾  pip
    (4)在pip文件夾裏面新建一個文件叫做  pip.ini ,內容寫如下即可
        [global]
        timeout = 6000
        index-url = https://mirrors.aliyun.com/pypi/simple/
        trusted-host = mirrors.aliyun.com
   - linux
    (1)cd ~
    (2)mkdir ~/.pip
    (3)vi ~/.pip/pip.conf
    (4)編輯內容,和windows一模一樣
- 需要安裝:pip install bs4
     bs4在使用時候需要一個第三方庫,把這個庫也安裝一下
     pip install lxml
  • 基礎使用
使用流程:       
    - 導包:from bs4 import BeautifulSoup
    - 使用方式:可以將一個html文檔,轉化為BeautifulSoup對象,然後通過對象的方法或者屬性去查找指定的節點內容
        (1)轉化本地文件:
             - soup = BeautifulSoup(open(‘本地文件‘), ‘lxml‘)
        (2)轉化網絡文件:
             - soup = BeautifulSoup(‘字符串類型或者字節類型‘, ‘lxml‘)
        (3)打印soup對象顯示內容為html文件中的內容

基礎鞏固:
    (1)根據標簽名查找
        - soup.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")
        - 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選擇器返回永遠是列表,需要通過下標提取指定的對象
  • 需求:使用bs4實現將詩詞名句網站中三國演義小說的每一章的內容爬去到本地磁盤進行存儲 http://www.shicimingju.com/book/sanguoyanyi.html
    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    import requests
    from bs4 import BeautifulSoup
    
    headers={
             ‘User-Agent‘: ‘Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36‘,
         }
    def parse_content(url):
        #獲取標題正文頁數據
        page_text = requests.get(url,headers=headers).text
        soup = BeautifulSoup(page_text,‘lxml‘)
        #解析獲得標簽
        ele = soup.find(‘div‘,class_=‘chapter_content‘)
        content = ele.text #獲取標簽中的數據值
        return content
    
    if __name__ == "__main__":
         url = ‘http://www.shicimingju.com/book/sanguoyanyi.html‘
         reponse = requests.get(url=url,headers=headers)
         page_text = reponse.text
    
         #創建soup對象
         soup = BeautifulSoup(page_text,‘lxml‘)
         #解析數據
         a_eles = soup.select(‘.book-mulu > ul > li > a‘)
         print(a_eles)
         cap = 1
         for ele in a_eles:
             print(‘開始下載第%d章節‘%cap)
             cap+=1
             title = ele.string
             content_url = ‘http://www.shicimingju.com‘+ele[‘href‘]
             content = parse_content(content_url)
    
             with open(‘./sanguo.txt‘,‘w‘) as fp:
                 fp.write(title+":"+content+‘\n\n\n\n\n‘)
                 print(‘結束下載第%d章節‘%cap)

爬蟲-三種數據解析方式