天天看點

Python爬蟲urllib使用及頁面解析

1.urllib庫

1.1 基本使用

使用urllib來擷取百度首頁的源碼

import urllib.request

# 1、定義一個url  就是你要通路的位址
url = 'http://www.baidu.com'

# 2、模拟浏覽器向伺服器發送請求 response響應
response = urllib.request.urlopen(url)

# 3、擷取響應中的頁面的源碼
content = response.read().decode('utf-8')

# 4、列印資料
print(content)
           
read方法,傳回的是位元組形式的二進制資料,我們要将二進制的資料轉換為字元串,需解碼 : decode(‘編碼的格式’)

1.2 1個類型和6個方法

import urllib.request

url = 'http://www.baidu.com'

# 模拟浏覽器向伺服器發送請求
response = urllib.request.urlopen(url)

# 一個類型:response是HTTPResponse的類型
print(type(response))

# 按照一個位元組一個位元組的去讀
content = response.read()
print(content)

# 傳回多少個位元組
content = response.read(5)
print(content)

# 讀取一行
content = response.readline()
print(content)

# 一行一行讀取 直至結束
content = response.readlines()
print(content)

# 傳回狀态碼  如果是200了 那麼就證明我們的邏輯沒有錯
print(response.getcode())

# 傳回的是url位址
print(response.geturl())

# 擷取是一個狀态資訊
print(response.getheaders())
           

一個類型:HTTPResponse

六個方法: read、readline、readlines、getcode、geturl、getheaders

1.3 下載下傳

import urllib.request

# 下載下傳網頁
url_page = 'http://www.baidu.com'

# url代表的是下載下傳的路徑  filename檔案的名字
urllib.request.urlretrieve(url_page,'baidu.html')

# 下載下傳圖檔
url_img = 'https://img1.baidu.com/it/u=3004965690,4089234593&fm=26&fmt=auto&gp=0.jpg'
urllib.request.urlretrieve(url= url_img,filename='lisa.jpg')

# 下載下傳視訊
url_video = 'https://vd3.bdstatic.com/mda-mhkku4ndaka5etk3/1080p/cae_h264/1629557146541497769/mda-mhkku4ndaka5etk3.mp4?v_from_s=hkapp-haokan-tucheng&auth_key=1629687514-0-0-7ed57ed7d1168bb1f06d18a4ea214300&bcevod_channel=searchbox_feed&pd=1&pt=3&abtest='

urllib.request.urlretrieve(url_video,'hxekyyds.mp4')
           
在python中,可以寫變量的名字,也可以直接寫值

1.4 請求對象的定制

import urllib.request

url = 'https://www.baidu.com'

headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36'
}

# 因為urlopen方法中不能存儲字典 是以headers不能傳遞進去
# 請求對象的定制
request = urllib.request.Request(url=url,headers=headers)
response = urllib.request.urlopen(request)
content = response.read().decode('utf8')
print(content)
           

1.5 get請求的quote方法

get請求參數,如果是中文,需要對中文進行編碼,如下面這樣,如果不編碼會報錯。

需求 擷取 https://www.baidu.com/s?wd=周傑倫的網頁源碼

編碼後如下: https://www.baidu.com/s?wd=%E5%91%A8%E6%9D%B0%E4%BC%A6

import urllib.request
import urllib.parse

url = 'https://www.baidu.com/s?wd='

# 請求對象的定制為了解決反爬的第一種手段
headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36'
}

# 将周傑倫三個字變成unicode編碼的格式,需要依賴于urllib.parse
name = urllib.parse.quote('周傑倫')

# 将轉碼後的字元串拼接到路徑後面
url = url + name

# 請求對象的定制
request = urllib.request.Request(url=url,headers=headers)

# 模拟浏覽器向伺服器發送請求
response = urllib.request.urlopen(request)

# 擷取響應的内容
content = response.read().decode('utf-8')

# 列印資料
print(content)
           
quote适用于将中文轉碼成Unicode編碼

1.6 get請求的urlencode方法

urlencode應用場景:多個參數的時候。如下

https://www.baidu.com/s?wd=周傑倫&sex=男
# 擷取https://www.baidu.com/s?wd=%E5%91%A8%E6%9D%B0%E4%BC%A6&sex=%E7%94%B7的網頁源碼

import urllib.request
import urllib.parse

base_url = 'https://www.baidu.com/s?'

data = {
    'wd':'周傑倫',
    'sex':'男',
    'location':'中國台灣省'
}

new_data = urllib.parse.urlencode(data)

# 請求資源路徑
url = base_url + new_data

headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36'
}

# 請求對象的定制
request = urllib.request.Request(url=url,headers=headers)

# 模拟浏覽器向伺服器發送請求
response = urllib.request.urlopen(request)

# 擷取網頁源碼的資料
content = response.read().decode('utf-8')

# 列印資料
print(content)
           

1.7 post請求

import urllib.request
import urllib.parse

url = 'https://fanyi.baidu.com/sug'

headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36'
}

data = {
    'kw':'spider'
}

# post請求的參數,必須要進行編碼
data = urllib.parse.urlencode(data).encode('utf-8')
request = urllib.request.Request(url=url,data=data,headers=headers)

# 模拟浏覽器向伺服器發送請求
response = urllib.request.urlopen(request)

# 擷取響應的資料
content = response.read().decode('utf-8')

# 字元串--》json對象
import json
obj = json.loads(content)
print(obj)
           

post請求的參數 必須要進行編碼:data = urllib.parse.urlencode(data)

編碼之後 必須調用encode方法 : data = urllib.parse.urlencode(data).encode(‘utf-8’)

post的請求的參數,是不會拼接在url的後面的 ,而是需要放在請求對象定制的參數中:

​ request = urllib.request.Request(url=url,data=data,headers=headers)

1.8 異常

import urllib.request
import urllib.error

url = 'http://www.doudan1.com'

headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36'
}

try:
    request = urllib.request.Request(url = url, headers = headers)
    response = urllib.request.urlopen(request)
    content = response.read().decode('utf-8')
    print(content)
except urllib.error.HTTPError:
    print('系統正在更新。。。')
except urllib.error.URLError:
    print('我都說了 系統正在更新。。。')
           

1.9 handler

為什麼要學習handler?

  • urllib.request.urlopen(url) 不能定制請求頭
  • urllib.request.Request(url,headers,data) 可以定制請求頭
  • Handler:定制更進階的請求頭(随着業務邏輯的複雜 請求對象的定制已經滿足不了我們的需求,動态cookie和代理不能使用請求對象的定制)
# 需求 使用handler來通路百度  擷取網頁源碼

import urllib.request

url = 'http://www.baidu.com'
headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36'
}

request = urllib.request.Request(url = url,headers = headers)

# handler   build_opener  open

#(1)擷取hanlder對象
handler = urllib.request.HTTPHandler()

#(2)擷取opener對象
opener = urllib.request.build_opener(handler)

# (3) 調用open方法
response = opener.open(request)
content = response.read().decode('utf-8')
print(content)
           

1.10 代理

為什麼需要代理?因為有的網站是禁止爬蟲的,如果用真實的ip去爬蟲,容易被封掉。

import urllib.request

url = 'http://www.baidu.com/s?wd=ip'

headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36'
}

# 請求對象的定制
request = urllib.request.Request(url = url,headers= headers)

# 模拟浏覽器通路伺服器
# response = urllib.request.urlopen(request)

proxies = {
    'http':'118.24.219.151:16817'
}

# handler  build_opener  open
handler = urllib.request.ProxyHandler(proxies = proxies)
opener = urllib.request.build_opener(handler)
response = opener.open(request)

# 擷取響應的資訊
content = response.read().decode('utf-8')

# 儲存
with open('daili.html','w',encoding='utf-8')as fp:
    fp.write(content)
           
代理可以使用:快代理。可以使用代理池來代替一個代理

2.解析技術

2.1 xpath

xpath安裝及加載

1.安裝lxml庫

pip install lxml ‐i https://pypi.douban.com/simple

2.導入lxml.etree

from lxml import etree

3.etree.parse() 解析本地檔案

html_tree = etree.parse(‘XX.html’)

4.etree.HTML() 伺服器響應檔案

html_tree = etree.HTML(response.read().decode(‘utf‐8’)

5.解析擷取DOM元素

html_tree.xpath(xpath路徑)
按照xpath的chrome插件,使用 ctrl + shift + x 打開插件

xpath基本文法

1.路徑查詢

//:查找所有子孫節點,不考慮層級關系

/ :找直接子節點

2.謂詞查詢

//div[@id]

//div[@id=“maincontent”]

3.屬性查詢

//@class

4.模糊查詢

//div[contains(@id, “he”)]

//div[starts‐with(@id, “he”)]

5.内容查詢

//div/h1/text()

6.邏輯運算

//div[@id=“head” and @class=“s_down”]

//title | //price

示例:

from lxml import etree

# xpath解析本地檔案
tree = etree.parse('test.html')

# 查找ul下面的li
li_list = tree.xpath('//body/ul/li')

# 查找所有有id的屬性的li标簽,text()擷取标簽中的内容
li_list = tree.xpath('//ul/li[@id]/text()')

# 找到id為l1的li标簽  注意引号的問題
li_list = tree.xpath('//ul/li[@id="l1"]/text()')

# 查找到id為l1的li标簽的class的屬性值
li = tree.xpath('//ul/li[@id="l1"]/@class')

# 查詢id中包含l的li标簽
li_list = tree.xpath('//ul/li[contains(@id,"l")]/text()')

# 查詢id的值以l開頭的li标簽
li_list = tree.xpath('//ul/li[starts-with(@id,"c")]/text()')

#查詢id為l1和class為c1的
li_list = tree.xpath('//ul/li[@id="l1" and @class="c1"]/text()')

li_list = tree.xpath('//ul/li[@id="l1"]/text() | //ul/li[@id="l2"]/text()')
           

2.2 JsonPath

JsonPath隻能解析本地檔案。

jsonpath的安裝及使用

pip安裝:

pip install jsonpath

jsonpath的使用:

obj = json.load(open(‘json檔案’, ‘r’, encoding=‘utf‐8’))

ret = jsonpath.jsonpath(obj, ‘jsonpath文法’)

示例:

{
  "store": {
    "book": [
      {
        "category": "修真",
        "author": "六道",
        "title": "壞蛋是怎樣練成的",
        "price": 8.95
      },
      {
        "category": "修真",
        "author": "天蠶洋芋",
        "title": "鬥破蒼穹",
        "price": 12.99
      },
      {
        "category": "修真",
        "author": "唐家三少",
        "title": "鬥羅大陸",
        "isbn": "0-553-21311-3",
        "price": 8.99
      },
      {
        "category": "修真",
        "author": "南派三叔",
        "title": "星辰變",
        "isbn": "0-395-19395-8",
        "price": 22.99
      }
    ],
    "bicycle": {
      "author": "老馬",
      "color": "黑色",
      "price": 19.95
    }
  }
}
           

解析上面的json資料,具體文法,參考如下部落格:

https://blog.csdn.net/luxideyao/article/details/77802389

import json
import jsonpath

obj = json.load(open('jsonpath.json','r',encoding='utf-8'))

# 書店所有書的作者
author_list = jsonpath.jsonpath(obj,'$.store.book[*].author')

# 所有的作者
author_list = jsonpath.jsonpath(obj,'$..author')

# store下面的所有的元素
tag_list = jsonpath.jsonpath(obj,'$.store.*')

# store裡面所有東西的price
price_list = jsonpath.jsonpath(obj,'$.store..price')

# 第三個書
book = jsonpath.jsonpath(obj,'$..book[2]')

# 最後一本書
book = jsonpath.jsonpath(obj,'$..book[(@.length-1)]')

#  前面的兩本書
book_list = jsonpath.jsonpath(obj,'$..book[0,1]')
book_list = jsonpath.jsonpath(obj,'$..book[:2]')

# 條件過濾需要在()的前面添加一個?
#   過濾出所有的包含isbn的書。
book_list = jsonpath.jsonpath(obj,'$..book[?(@.isbn)]')

# 哪本書超過了10塊錢
book_list = jsonpath.jsonpath(obj,'$..book[?(@.price>10)]')
           

2.3 BeautifulSoup

基本介紹

  • BeautifulSoup簡稱:bs4
  • 什麼是BeatifulSoup? BeautifulSoup,和lxml一樣,是一個html的解析器,主要功能也是解析和提取資料
  • 優缺點

    缺點:效率沒有lxml的效率高

    優點:接口設計人性化,使用友善

安裝以及建立

  • 安裝
    pip install bs4 -i https://pypi.douban.com/simple
  • 導入
    from bs4 import BeautifulSoup
  • 建立對象
    • 伺服器響應的檔案生成對象
      soup = BeautifulSoup(response.read().decode(), ‘lxml’)
    • 本地檔案生成對象
      soup = BeautifulSoup(open(‘1.html’), ‘lxml’)
注意:預設打開檔案的編碼格式gbk是以需要指定打開編碼格式

節點定位

1.根據标簽名查找節點

soup.a # 隻能找到第一個a

soup.a.name

soup.a.attrs

2.函數

  • find(傳回一個對象)

    find(‘a’):隻找到第一個a标簽

    find(‘a’, title=‘名字’)

    find(‘a’, class_=‘名字’)

  • find_all(傳回一個清單)

    find_all(‘a’) :查找到所有的a

    find_all([‘a’, ‘span’]) 傳回所有的a和span

    find_all(‘a’, limit=2) 隻找前兩個a

  • select(根據選擇器得到節點對象)【☆☆☆】
    • element

      :p
    • .class

      :.firstname
    • #id

      :#firstname
    • 屬性選擇器

      [attribute]

      :li = soup.select(‘li[class]’)

      [attribute=value]

      :li = soup.select(‘li[class=“hengheng1”]’)
    • 層級選擇器

      :

      div p 後代選擇器

      div>p 子代選擇器:某标簽的第一級子标簽

      div,p div或p标簽的所有的對象

節點資訊

  • 擷取節點内容:适用于标簽中嵌套标簽的結構

    obj.string

    obj.get_text()【推薦】

  • 節點的屬性

    tag.name:擷取标簽名

    tag.attrs:将屬性值作為一個字典傳回

  • 擷取節點屬性

    obj.attrs.get(‘title’)【常用】

    obj.get(‘title’)

    obj[‘title’]

示例:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <div>
        <ul>
            <li id="l1">張三</li>
            <li id="l2">李四</li>
            <li>王五</li>
            <a href="" id="" class="a1">尚矽谷</a>
            <span>嘿嘿嘿</span>
        </ul>
    </div>
    <a href="" title="a2">百度</a>
    <div id="d1">
        <span>
            哈哈哈
        </span>
    </div>
    <p id="p1" class="p1">呵呵呵</p>
</body>
</html>
           

使用BeautifulSoup解析上面的html

from bs4 import BeautifulSoup

# 預設打開的檔案的編碼格式是gbk,是以在打開檔案的時候需要指定編碼
soup = BeautifulSoup(open('bs4的基本使用.html',encoding='utf-8'),'lxml')

# 根據标簽名查找節點,找到的是第一個符合條件的資料
print(soup.a)
# 擷取标簽的屬性和屬性值
print(soup.a.attrs)

# bs4的一些函數
# (1)find:傳回的是第一個符合條件的資料
print(soup.find('a'))

# 根據title的值來找到對應的标簽對象
print(soup.find('a',title="a2"))

# 根據class的值來找到對應的标簽對象  注意的是class需要添加下劃線
print(soup.find('a',class_="a1"))

# (2)find_all  傳回的是一個清單,并且傳回了所有的a标簽
print(soup.find_all('a'))

# 如果想擷取的是多個标簽的資料 那麼需要在find_all的參數中添加的是清單的資料
print(soup.find_all(['a','span']))

# limit的作用是查找前幾個資料
print(soup.find_all('li',limit=2))

# (3)select(推薦)
# select方法傳回的是一個清單,并且會傳回多個資料
print(soup.select('a'))

# 可以通過.代表class  我們把這種操作叫做類選擇器
print(soup.select('.a1'))
print(soup.select('#l1'))

# 屬性選擇器:通過屬性來尋找對應的标簽
# 查找到li标簽中有id的标簽
print(soup.select('li[id]'))

# 查找到li标簽中id為l2的标簽
print(soup.select('li[id="l2"]'))

# 層級選擇器
#  後代選擇器:找到的是div下面的li
print(soup.select('div li'))

# 子代選擇器:某标簽的第一級子标簽
print(soup.select('div > ul > li'))

# 找到a标簽和li标簽的所有的對象
print(soup.select('a,li'))

# 擷取節點内容
obj = soup.select('#d1')[0]

# 如果标簽對象中,隻有内容,那麼string和get_text()都可以使用
# 如果标簽對象中,除了内容還有标簽,那麼string就擷取不到資料 而get_text()是可以擷取資料
# 推薦使用get_text()
print(obj.string)
print(obj.get_text())

# 節點的屬性
obj = soup.select('#p1')[0]
# name是标簽的名字
print(obj.name)
# 将屬性值左右一個字典傳回
print(obj.attrs)

# 擷取節點的屬性
print(obj.attrs.get('class'))
print(obj.get('class'))
print(obj['class'])