溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Python自動化開發(fā)學(xué)習(xí)之如何實現(xiàn)爬蟲

發(fā)布時間:2021-10-18 15:36:54 來源:億速云 閱讀:96 作者:小新 欄目:編程語言

這篇文章主要介紹Python自動化開發(fā)學(xué)習(xí)之如何實現(xiàn)爬蟲,文中介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們一定要看完!

講師的博客:https://www.cnblogs.com/wupeiqi/articles/6283017.html

建立本地緩存

用下面的命令,就可以把一個頁面爬取下來。不過再繼續(xù)其他操作之前先把爬取的內(nèi)容在本地建立緩存:

import requests
r = requests.get('http://www.autohome.com.cn/news')  # 爬取頁面
print(r.text)  # 打印響應(yīng)的內(nèi)容

下面會試很多的方法,還是要避免每次都去爬一次相同的頁面。主要爬的太頻繁,不知道會不會被封。所以爬取過一次之后,在本地建立緩存,之后的各種分析就不用再去爬一遍了。
要緩存的就是 r = requests.get('http://www.autohome.com.cn/news') 這個,也就是這里的r這個對象。不緩存的話,r是保存在內(nèi)存中的,程序一旦退出就沒有了。這里要做的就是對r這個對象進行序列化,把它保存為本地的文件。由于r是一個python對象,無法使用JSON序列化,這里可以用pickle,保存為一個二進制文件。

序列化與反序列化

首先是把對象序列化,保存為本地的二進制文件:

import pickle
with open('test.pk', 'wb') as f:
    pickle.dump(r, f)

只有再用的時候,就不需要再通過requests.get再去爬一遍了,直接從本地文件中取出內(nèi)容反序列生成r對象:

import pickle
with open('test.pk', 'rb') as f:
    r = pickle.load(f)

封裝個模塊

然后,每次自己都要想一下之前有沒有緩存過也很麻煩,所以在封裝一下,自動判斷有沒有緩存過。如果沒有就去爬網(wǎng)頁,然后生成緩存。如果有就去緩存的文件里讀。
創(chuàng)建一個文件夾“pk”專門存放緩存的文件。假設(shè)測試的python文件是 s1.py 那么就生成一個 pk/s1.pk 的緩存文件,只要判斷是否存在該文件,就可以知道是否緩存過了:

import os
import pickle
import requests

def get_pk_name(path):
    basedir = os.path.dirname(path)
    fullname = os.path.basename(path)
    name = os.path.splitext(fullname)[0]
    pk_name = '%s/pk/%s.%s' % (basedir, name, 'pk')
    return pk_name

pk_name = get_pk_name(__file__)
response = None
if os.path.exists(pk_name):
    print("已經(jīng)爬取過了,獲取緩存的內(nèi)容...")
    with open(pk_name, 'rb') as f:
        response = pickle.load(f)

# 只有在沒有緩存過頁面的時候才進行爬取
if not response:
    print("開始爬取頁面...")
    response = requests.get('http://www.autohome.com.cn/news')
    # 爬完之后記得保存,下次就不用再去爬取了
    with open(pk_name, 'wb') as f:
        pickle.dump(response, f)

# 從這里開始寫真正的代碼
print(response.text)

Requests

中文官方文檔:http://cn.python-requests.org/zh_CN/latest/user/quickstart.html
安裝模塊:

pip install requests

發(fā)送請求

r = requests.get('http://www.autohome.com.cn/news')

讀取響應(yīng)內(nèi)容

print(r.text)

文本編碼
上面可能會有亂碼,那就是編碼不對,可以查看當前的編碼,也可以改變它。默認的編碼就是 'ISO-8859-1' :

print(r.encoding)
r.encoding = 'ISO-8859-1'

另外還可以自動獲取頁面的編碼,解決亂碼問題:

r.encoding = r.apparent_encoding
print(r.text)

二進制響應(yīng)內(nèi)容
如果要自己找編碼,應(yīng)該也是在這里面找

print(r.content)

在下載的時候,就要用到二進制的響應(yīng)內(nèi)容了
響應(yīng)狀態(tài)碼

print(r.status_code)

正常返回的狀態(tài)碼是200
Cookie

cookie_obj = r.cookies
cookie_dict = r.cookies.get_dict()

r.cookies 是一個對象,這個對象的的行為和字典類似,也可以像對象那樣使用。這里還可以用 get_dict() 方法轉(zhuǎn)成原生的字典。

Beautiful Soup

中文官方文檔:https://beautifulsoup.readthedocs.io/
安裝模塊:

pip install beautifulsoup4

這里繼續(xù)對上面爬取到的內(nèi)容進行分析,把爬取到的內(nèi)容先把編碼轉(zhuǎn)正確了,然后這里要分析的是 r.text 文本的響應(yīng)內(nèi)容:

import requests
from bs4 import BeautifulSoup

r = requests.get('http://www.autohome.com.cn/news')
r.encoding = r.apparent_encoding
soup = BeautifulSoup(r.text, features='html.parser')

features 參數(shù)是指定一個處理引擎,這里用的是默認的,效率一般,但是不用額外的安裝。如果是生產(chǎn)環(huán)境,還有更高效的處理引擎。
這里最后拿到了一個 soup 對象,之后又一系列的方法,可以提取出各種內(nèi)容。

查找方法
soup.find方法,可以找到第一個符合條件的對象??梢哉覙撕灒部梢哉襥d等,還可以多條件組合使用:

soup.find("div")
soup.find(id="link3")
soup.find("div", id="link3")

soup.find_all方法,和find的用法一樣,實際上find方法的實現(xiàn)也是調(diào)用find_all方法。find_all方法會返回所有符合條件的對象,返回的對象是在一個列表里的。

打印對象和對象的文本
直接打印對象會打印整個html標簽,如果只需要標簽中的文本,可以通過對象的text屬性:

soup = BeautifulSoup(r.text, features='html.parser')
target = soup.find('div', {'class': "article-bar"})
print(type(target), target, target.text)

獲取對象的所有屬性
對象的attrs屬性里是這個html標簽的所有的屬性:

target = soup.find(id='auto-channel-lazyload-article')
print(target.attrs)

獲取屬性的值
用get方法可以通過屬性的key獲取到對應(yīng)的value。下面2個方法都可以:

v1 = target.get('name')
v2 = target.attrs.get('value')

# get方法的源碼
    def get(self, key, default=None):
        """Returns the value of the 'key' attribute for the tag, or
        the value given for 'default' if it doesn't have that
        attribute."""
        return self.attrs.get(key, default)

實戰(zhàn)

僅憑上面這點知識點就可以開始下面的實戰(zhàn)了

爬取汽車之家新網(wǎng)咨詢

下面是代碼,找到了沒一條新聞咨詢的a連接的地址,以及標題,最后還把對應(yīng)的圖片下載到了本地(先建一個img文件夾):

# check_cache.py
"""用來檢查是否有本地緩存的小模塊"""

import os

def get_pk_name(path):
    basedir = os.path.dirname(path)
    fullname = os.path.basename(path)
    name = os.path.splitext(fullname)[0]
    pk_name = '%s/pk/%s.%s' % (basedir, name, 'pk')
    return pk_name

# s1.py
"""爬取汽車之家新網(wǎng)咨詢"""

import os
import pickle
import requests
from bs4 import BeautifulSoup

from check_cache import get_pk_name

pk_name = get_pk_name(__file__)
response = None
if os.path.exists(pk_name):
    print("已經(jīng)爬取過了,獲取緩存的內(nèi)容...")
    with open(pk_name, 'rb') as f:
        response = pickle.load(f)

# 只有在沒有緩存過頁面的時候才進行爬取
if not response:
    print("開始爬取頁面...")
    response = requests.get('http://www.autohome.com.cn/news')
    # 爬完之后記得保存,下次就不用再去爬取了
    with open(pk_name, 'wb') as f:
        pickle.dump(response, f)

response.encoding = response.apparent_encoding  # 獲取頁面的編碼,解決亂碼問題
# print(response.text)

soup = BeautifulSoup(response.text, features='html.parser')
target = soup.find(id='auto-channel-lazyload-article')
# print(target)
# obj = target.find('li')
# print(obj)
li_list = target.find_all('li')
# print(li_list)
for i in li_list:
    a = i.find('a')
    # print(a)
    # print(a.attrs)  # 有些li標簽里沒有a標簽,所以可能會報錯
    if a:  # 這樣判斷一下就好了
        # print(a.attrs)  # 這是一個字典
        print(a.attrs.get('href'))  # 那就用操作字典的方法來獲取值
        # tittle = a.find('h4')  # 這個類型是對象
        tittle = a.find('h4').text  # 這樣拿到的才是文本
        print(tittle, type(tittle))  # 不過打印出來差不多,都會變成字符串,差別就是h4這個標簽
        img_url = a.find('img').attrs.get('src')
        print(img_url)
        # 上面獲取到了圖片的url,現(xiàn)在可以下載到本地了
        img_response = requests.get("http:%s" % img_url)
        if '/' in tittle:
            file_name = "img/%s%s" % (tittle.replace('/', '_'), os.path.splitext(img_url)[1])
        else:
            file_name = "img/%s%s" % (tittle, os.path.splitext(img_url)[1])
        with open(file_name, 'wb') as f:
            f.write(img_response.content)

登錄抽屜

這里要解決一個登錄的問題。
登錄有2種,一種是Form表單驗證,還有一種是AJAX請求。這是一個使用AJAX做登錄請求的網(wǎng)站。
下面是幾張瀏覽器調(diào)試工具的截圖,主要是要找一下,登錄請求需要提交到哪里,提交哪些信息,以及最后會返回的內(nèi)容。
登錄的AJAX請求:
Python自動化開發(fā)學(xué)習(xí)之如何實現(xiàn)爬蟲

請求正文:
Python自動化開發(fā)學(xué)習(xí)之如何實現(xiàn)爬蟲

響應(yīng)正文:
Python自動化開發(fā)學(xué)習(xí)之如何實現(xiàn)爬蟲

登錄請求的代碼如下:

import requests

post_dict = {
    'phone': '8613507293881',  # 從請求正文里發(fā)現(xiàn),會在手機號前加上86
    'password': '123456',
}

# 所有的請求頭可以從請求標頭里找到,不過不是必須的
headers = {
    'User-Agent': '',  # 這個網(wǎng)站要驗證這個請求頭,不過只要有就可以通過
}

# 從標頭里可以得知,請求的url和請求的方法
response = requests.post(
    url='https://dig.chouti.com/login',
    data=post_dict,
    headers=headers,
)

print(response.text)
# 這里還有返回的cookies信息,登錄成功關(guān)鍵是要拿到成功的cookie
cookie_dict = response.cookies.get_dict()
print(cookie_dict)

登錄的套路
上面使用了錯誤的用戶名和密碼,在繼續(xù)登錄驗證之前,看了解下登錄的機制。
登錄肯定是要提交驗證信息的,一般就用戶名和密碼。然后請求驗證之后,服務(wù)端會記錄一個session,然后會返回給客戶端一個cookie。之后用戶每次請求都帶著這個cookie,服務(wù)端收到請求后就知道這個請求是那個用戶提交的了。
不過這個網(wǎng)站有一點不一樣,用戶在提交驗證信息的時候,不但要提交用戶名和密碼,還要提交一個gpsd。然后服務(wù)端驗證通過后,會把這次收到的gpsd記錄下來。用戶之后的cookie里就是要帶著這個gpsd就能驗證通過。驗證請求的gpsd可以從第一次發(fā)送get請求的返回的cookie里獲取到。另外用戶驗證通過后,服務(wù)端會返回一個cookie,這個cookie里也有一個gpsd,但是是一個新的gpsd,并且是沒有用的,這里就會混淆我們,在進行驗證這不的時候造成一些困擾。
具體如何應(yīng)對這類特殊情況,只能用瀏覽器,打開調(diào)試工具,然后一點一點試了。

登錄并點贊
下面就是登錄驗證,獲取到第一條咨詢的標題和id,發(fā)送post請求點贊:

import requests
from bs4 import BeautifulSoup

headers = {
    'User-Agent': '',  # 這個網(wǎng)站要驗證這個請求頭,不過只要有就可以通過
}

r1 = requests.get('https://dig.chouti.com', headers=headers)
r1_cookies = r1.cookies  # 這里有個gpsd,登錄驗證的時候要一并提交
print(r1_cookies.get_dict())

# 不能把密碼上傳啊
with open('password/s2.txt') as f:
    auth = f.read()
    auth = auth.split('\n')

post_dict = {
    'phone': '86%s' % auth[0],  # 從請求正文里發(fā)現(xiàn),會在手機號前加上86
    'password': auth[1],
}

# 這個網(wǎng)站的登錄機制是,發(fā)送驗證信息和cookies里的gpsd,成功后給你的gpsd授權(quán)
# 之后的請求只有cookies里有這個授權(quán)過的gpsd就能認證通過
r2 = requests.post(
    url='https://dig.chouti.com/login',
    data=post_dict,
    headers=headers,
    cookies={'gpsd': r1_cookies['gpsd']}
)

print(r2.text)
r2_cookies = r2.cookies  # 這里也會返回一個新的gpsd,但是無用。
print(r2_cookies.get_dict())

# 獲取咨詢,然后點贊
r3 = requests.get(
    url='https://dig.chouti.com',
    headers=headers,
    cookies={'gpsd': r1_cookies['gpsd']},
)
r3.encoding = r3.apparent_encoding
soup = BeautifulSoup(r3.text, features='html.parser')
target = soup.find(id='content-list')
item = target.find('div', {'class': 'item'})  # 就只給第一條點贊吧
news = item.find('a', {'class': 'show-content'}).text
linksId = item.find('div', {'class': 'part2'}).attrs['share-linkid']
print('news:', news.strip())

# 點贊
r = requests.post(
    url='https://dig.chouti.com/link/vote?linksId=%s' % linksId,
    headers=headers,
    cookies={
        'gpsd': r1_cookies['gpsd'],
    }
)

print(r.text)

Requests 模塊詳細

找到requests.get()方法的源碼,在 requests/api.py 這個文件里,有如下這些方法:

  • requests.get()

  • requests.options()

  • requests.head()

  • requests.post()

  • requests.put()

  • requests.patch()

  • requests.delete()

另外還有一個 requests.request() 方法。上面這些方法里最終調(diào)用的都是這個request方法。下面就來看下這些方法里都提供了寫什么參數(shù)。

參數(shù)

在 requests.request() 方法里所有的參數(shù)如下:

  • method : 提交方式。request方法里的參數(shù),其他方法里在調(diào)用request方法時,都會填好。

  • url : 提交地址

  • params : 在url中傳遞的參數(shù)。也就是get方式的參數(shù)

  • data : 在請求體里傳遞的參數(shù),F(xiàn)orm表單提交的內(nèi)容。

  • json : 在請求體里傳遞的參數(shù),AJAX提交的內(nèi)容。和data不同,會把參數(shù)序列化后,把整個字符串發(fā)出去。

  • headers : 請求頭。有幾個重要的請求頭信息,下面會列出

  • cookies : 這個就是Cookies。它是放在請求頭的Cookie里發(fā)送給服務(wù)端的。

  • files : 上傳文件。下面有使用示例

  • auth : 設(shè)置 HTTP Auth 的認證信息。下面有展開

  • timeout : 超時時間。單位是秒,類型是float。有連接超時和等待返回超時,同時會設(shè)置這兩個時間。也可以是個元祖分別設(shè)置兩個時間(connect timeout, read timeout)

  • allow_redirects : 是否允許重定向。默認是True。

  • proxies : 使用代理。下面有展開

  • verify : 對于https的請求,如果設(shè)為Flase,會忽略證書。

  • stream : 下載時的參數(shù),如果是False,則先一次全部下載到內(nèi)存。如果內(nèi)容太大,下面有展開。

  • cert : 提交請求如果需要附帶證書文件,則要設(shè)置cert。

data 和 json 參數(shù)
這兩個參數(shù)都是在請求體力傳遞的參數(shù)。但是格式不同,在網(wǎng)絡(luò)上最終傳遞的一定都是序列化的字符串。不同的類型會生成一個不同的請求頭。在 requests/models.py 文件里可以找到如下的代碼:

if not data and json is not None:
    content_type = 'application/json'

if data:
    if isinstance(data, basestring) or hasattr(data, 'read'):
        content_type = None
    else:
        content_type = 'application/x-www-form-urlencoded'

也就是不同的格式,會設(shè)置不同的 Content-Type 請求頭:
data 請求頭:'application/x-www-form-urlencoded'
json 請求頭:'application/json'
而后端收到請求后,也就可以先查找請求頭里的 Content-Type ,然后再解析請求體里的數(shù)據(jù)。
為什么要用兩種格式?
Form表單提交的是data數(shù)據(jù),并且Form只能提交字符串或列表,是沒有字典的。也就是data這個字典里的value的值只能是字符串或列表,不能是字典。(data字典里不能套字典)
如果就是需要向后端提交一個字典的話,那么只能使用josn了。

請求頭

  • Referer : 上一次請求的url

  • User-Agent : 客戶端使用的瀏覽器

發(fā)送文件
這是最基本的用法,字典的key f1,就是Form表單的name。這里實例用了request方法來提交請求,之后的例子只有file_dict不同:

file_dict = {
    'f1': open('test1.txt', rb)
}
requests.request(
    method='POST',
    url='http://127.0.0.1:8000/test/',
    files=file_dict
)

定制文件名:

file_dict = {
    'f2': ('mytest.txt', open('test2.txt', rb))
}

定制文件內(nèi)容(沒有文件對象了,文件名當然也得自己定了):

file_dict = {
    'f3': ('test3.txt', "自己寫內(nèi)容,或者從文件里讀取到的內(nèi)容")
}

HTTP Auth
HTTP Auth是一種基本連接認證。比如家里用的路由器、ap,用web登錄時會彈框(基本登錄框,這個不是模態(tài)對話框),就是這種認證方式。它會把用戶名和密碼通過base64加密后放在請求頭的 Authorization 里發(fā)送出去。
使用的示例代碼:

import requests

def param_auth():
    from requests.auth import HTTPBasicAuth

    ret = requests.get('https://api.github.com/user', auth=HTTPBasicAuth('wupeiqi', 'sdfasdfasdf'))
    print(ret.text)

在 requests.auth 里看到了幾個類,應(yīng)該是不同的加密或者認證方式,但是本質(zhì)都是把認證信息加密后放在請求頭里發(fā)送。這里就用 HTTPBasicAuth 舉例了。下面是 HTTPBasicAuth 的源碼:

class HTTPBasicAuth(AuthBase):
    """Attaches HTTP Basic Authentication to the given Request object."""

    def __init__(self, username, password):
        self.username = username
        self.password = password

    def __eq__(self, other):
        return all([
            self.username == getattr(other, 'username', None),
            self.password == getattr(other, 'password', None)
        ])

    def __ne__(self, other):
        return not self == other

    def __call__(self, r):
        r.headers['Authorization'] = _basic_auth_str(self.username, self.password)
        return r

上面的過程很簡單,把用戶名和密碼通過 _basic_auth_str 方法加密后,加到請求頭的 'Authorization' 里。
這種認證方式比較簡單,發(fā)布到公網(wǎng)上的網(wǎng)站不會用這種認證方式。

proxies 代理
把代理的設(shè)置都寫在一個字典里,使用代理的設(shè)置如下:

import requests

proxies1 = {
    'http': '61.172.249.96:80',  # http的請求用這個代理
    'https': 'http://61.185.219.126:3128',  # https的請求用這個代理
}
proxies2 = {'http://10.20.1.128': 'http://10.10.1.10:5323'}  # 這特定的站定使用代理
r = requests.get('http://www.google.com', proxies=proxies1)

如果是需要用戶名和密碼的代理,需要用到上面的auth,這里auth也是一樣,是放在請求頭里的:

from requests.auth import HTTPProxyAuth

auth = HTTPProxyAuth('my_username', 'my_password')  # 這里一次輸入用戶名和密碼
r = requests.get('http://www.google.com', proxies=proxies1, auth=auth)

stream 下載
發(fā)送完請求,不立即下載全部內(nèi)容(一次把完整的內(nèi)容全部下載到內(nèi)存)。而是通過迭代的方式,一點一點進行下載:

import requests

def param_stream():
    from contextlib import closing
    with closing(requests.get('http://httpbin.org/get', stream=True)) as r:
        # 在此處理響應(yīng)。
        for i in r.iter_content():
            print(i)  # 這里用二進制打開個文件寫,應(yīng)該就好了

Session

多次請求的時候,使用 requests.Session() 會自動幫我們管理好Cookie,另外還會設(shè)置好一些默認信息,比如請求頭等等。
用法如下:

import requests

session = requests.Session()  # 生成一個session實例
# 之后的requests請求,使用session替代requests,比如get請求如下
r1 = session.get('https://dig.chouti.com')

不如看下源碼:

class Session(SessionRedirectMixin):
    """A Requests session.

    Provides cookie persistence, connection-pooling, and configuration.

    Basic Usage::

      >>> import requests
      >>> s = requests.Session()
      >>> s.get('http://httpbin.org/get')
      <Response [200]>

    Or as a context manager::

      >>> with requests.Session() as s:
      >>>     s.get('http://httpbin.org/get')
      <Response [200]>
    """

    __attrs__ = [
        'headers', 'cookies', 'auth', 'proxies', 'hooks', 'params', 'verify',
        'cert', 'prefetch', 'adapters', 'stream', 'trust_env',
        'max_redirects',
    ]

除了實例化后使用,還可以像文件操作一樣用with的方法使用。
attrs 列表里的值,就是session會自動幫我們設(shè)置的所有的屬性。
比如headers,它會默認在每次發(fā)送的時候添加如下的請求頭:

def default_headers():
    """
    :rtype: requests.structures.CaseInsensitiveDict
    """
    return CaseInsensitiveDict({
        'User-Agent': default_user_agent(),
        'Accept-Encoding': ', '.join(('gzip', 'deflate')),
        'Accept': '*/*',
        'Connection': 'keep-alive',
    })

# User-Agent 的值是這樣的,"python-requests/2.19.1" 后面是requests模塊的軟件版本,會變。
# 可以方便的改掉
s = requests.Session()
s.headers['User-Agent'] = ""

學(xué)到這里,之后再發(fā)送請求,尤其是要和網(wǎng)站進行多次交互的。就新把Session設(shè)置好,然后用Session來請求。所有的設(shè)置都會保存在Session的實例里,重復(fù)使用,自動管理。

優(yōu)化登錄點贊

之前自動登錄點贊的例子,如果使用session改一下就簡單多了,完全不用管cookie:

import requests
from bs4 import BeautifulSoup

session = requests.Session()

# 默認的 User-Agent 的值是 "python-requests/2.19.1" 會被反爬,需要改一下
session.headers['User-Agent'] = ""
session.get('https://dig.chouti.com')

# 不能把密碼上傳啊
with open('password/s2.txt') as f:
    auth = f.read()
    auth = auth.split('\n')

post_dict = {
    'phone': '86%s' % auth[0],  # 從請求正文里發(fā)現(xiàn),會在手機號前加上86
    'password': auth[1],
}
session.post('https://dig.chouti.com/login', data=post_dict)

# 獲取咨詢,然后點贊
r3 = session.get('https://dig.chouti.com')
r3.encoding = r3.apparent_encoding
soup = BeautifulSoup(r3.text, features='html.parser')
target = soup.find(id='content-list')
item = target.find('div', {'class': 'item'})
news = item.find('a', {'class': 'show-content'}).text
linksId = item.find('div', {'class': 'part2'}).attrs['share-linkid']
print('news:', news.strip())

# 點贊
r = session.post('https://dig.chouti.com/link/vote?linksId=%s' % linksId)
print(r.text)

以上是“Python自動化開發(fā)學(xué)習(xí)之如何實現(xiàn)爬蟲”這篇文章的所有內(nèi)容,感謝各位的閱讀!希望分享的內(nèi)容對大家有幫助,更多相關(guān)知識,歡迎關(guān)注億速云行業(yè)資訊頻道!

向AI問一下細節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI