python爬蟲抓取時常見的小問題總結(jié)
前言:
現(xiàn)在寫爬蟲,入門已經(jīng)不是一件門檻很高的事情了,網(wǎng)上教程一大把,但很多爬蟲新手在爬取數(shù)據(jù)的過程中依舊會遇到這樣那樣的問題。
今天整理了部分新手在爬蟲過程中遇到的問題,希望可以給大家提供一點問題解決的思路和參考。
01 無法正常顯示中文?
使用requests庫或者urllib庫獲取源代碼時無法正常顯示中文;
r = requests.get('http://xxx')print r.text
使用requests解析中文網(wǎng)頁時,上述語句在ipython中一直打印的都是亂碼.......
試過如下代碼:
import sys reload(sys) sys.setdefaultencoding('utf8')
還有類似:
r.text.decode('coding1').encoding('coding2')
都不能湊效!
解決方法
① requests庫的文本中有兩種類型
- 一種是文本類型,使用text屬性,一種是針對音頻、視頻、圖片等二進制數(shù)據(jù)類型,使用content屬性。
- 一般返回的是text屬性時會出現(xiàn)中文亂碼現(xiàn)象,因此在輸出返回之前需要顯示的修改屬性encoding,將其賦值為“utf-8”或者是apparent_encoding即可。
② urllib庫的文本只有一種就是使用read()方法進行讀取
因此要解決中文問題,一定要在讀取后加入.decode(“utf-8”),進行顯示的轉(zhuǎn)碼之后便不會出現(xiàn)亂碼問題了。
02 加密問題
爬蟲一般怎么解決加密問題?
① 對于網(wǎng)頁端來說通常加密的算法是寫在 js 代碼里的,所以首先你要對 js 語言有所了解。
至少知道 js 基礎(chǔ)的內(nèi)容,其次找到對應(yīng) js 加密代碼,然后找出關(guān)鍵的函數(shù)。
把 js 代碼在 node.js 環(huán)境進行調(diào)試,最后在 Python 環(huán)境下利用 execjs 庫去執(zhí)行調(diào)試好的代碼。
② 就是模擬瀏覽器環(huán)境直接獲取渲染后的數(shù)據(jù),最常用的手段就是利用 Selenium 框架了。
這種方式非常便利,當(dāng)然對應(yīng)的缺點就是效率非常低下。不過現(xiàn)在有新的框架來取代 Selenium,即 Puppeteer,這個框架你可以看出是異步版的 Selenium。
對于爬蟲程序,一個萬能公式:
爬蟲程序 = 網(wǎng)絡(luò)請求 + 數(shù)據(jù)解析 + 數(shù)據(jù)存儲
這三部分就對應(yīng)這爬蟲的基礎(chǔ),任何一個爬蟲程序都會保存這三部分的內(nèi)容,一些復(fù)雜的爬蟲無非是在此基礎(chǔ)上添加些別的內(nèi)容。一個爬蟲工程師反爬能力有多強,他的爬蟲實力就有多高。
03 獲取不到網(wǎng)頁的全部代碼?
問題:通過request方法獲取的網(wǎng)頁代碼與在瀏覽器看到的網(wǎng)頁源碼不一致;
解決方法:由于很多網(wǎng)頁的數(shù)據(jù)傳輸是通過js命令傳到網(wǎng)頁的,因此使用request()方法不能獲取通過js傳遞過來的信息代碼。此時通過使用selenium庫來模擬瀏覽器運行,就像真正的用戶在操作一樣,可通過此方法可獲得該網(wǎng)頁的源碼。
from selenium.webdriver.support.wait import WebDriverWaitbrowser = webdriver.Chrome()browser.get(Url)html = browser.page_source
04 點擊下一頁時網(wǎng)頁網(wǎng)頁不變
問題:
在爬取的過程中,由于要爬取每個律師詳細(xì)信息所對應(yīng)的網(wǎng)址,因此涉及到翻頁的問題,但在網(wǎng)頁上實時點擊下一頁時發(fā)現(xiàn)網(wǎng)址并沒有發(fā)生變化。
解決方法:
通過使用selenium中nextpagebutton.click()方法來模擬跳轉(zhuǎn)到下一頁,從而對下一頁的內(nèi)容進行獲取。
nextpagebutton = browser.find_element_by_xpath('//*[@class="next_page"]') # 定位到“下一頁”按鈕 nextpagebutton.click() # 模擬點擊下一頁 wait = WebDriverWait(browser, 10) # 瀏覽器等待10s
05 文本節(jié)點問題
首先看兩個HTML代碼,這是你眼中的HTML代碼:
這是計算機眼中的HTML代碼:
解決方法:
在BS4中,我們在HTML中看到的換行符以及空格都是NavigableString 也就是文本節(jié)點。
06 如何快速找到提取數(shù)據(jù)?
解析網(wǎng)頁時,如何快速找到數(shù)據(jù)存放的位置,并提取其中的數(shù)據(jù)?
這是很多新手會遇到的一個問題;就是雖然運行我的代碼沒有問題,大概邏輯也能讀得懂,但是想修改一下爬取同類網(wǎng)站,或者同一個網(wǎng)站的其他數(shù)據(jù)時卻不知從何下手。
這個其實是對工具使用不熟悉而已;我這里簡單講解一下(beautifulSoup)常用的使用技巧,當(dāng)然它有很多強大便捷的功能。我這里只介紹幾個常用的函數(shù),這幾個用好了一樣可以應(yīng)付幾乎所有網(wǎng)站。首先,爬取之前需要定位到數(shù)據(jù)所在的標(biāo)簽,這個使用 F12 開發(fā)者工具中的這個按鈕,點一下按鈕,然后點一下網(wǎng)頁,可以很快定位到頁面中的相應(yīng)標(biāo)簽。這步就不詳細(xì)說了,很簡單的,自己摸索一下。
接下來介紹如何用代碼獲取那個標(biāo)簽;
首先你觀察你要找到的標(biāo)簽,是什么標(biāo)簽?是否有 class 或者 id 這樣的屬性(如果沒有就找找它父標(biāo)簽有沒有,盡量找這樣的)因為 class 和 id 這兩個屬性作為篩選條件的話,查找到的干擾項極少,運氣好的話,基本上可以一擊必中。
這里介紹 beautifulSoup 中的兩個函數(shù),find 和 find_all 函數(shù);
比如我們要獲取上圖中箭頭所指的,id 為 ozoom 的 div 標(biāo)簽時,我們可以這樣:
# html 是之前發(fā)起請求獲取到的網(wǎng)頁內(nèi)容 bsobj = bs4.BeautifulSoup(html,'html.parser') # 獲取 id 為 ozoom 的 div 標(biāo)簽 # 根據(jù) id 查找標(biāo)簽 div = bsobj.find('div', attrs = {'id' : 'ozoom'}) # 繼續(xù)獲取 div 下的 class 為 list_t 的 div 標(biāo)簽 # 根據(jù) class 查找標(biāo)簽 title = div.find('div', attrs = {'class': 'list_t'})
注意:
如果標(biāo)簽有 id 屬性的話盡量用 id 來查找,因為整個頁面 id 是唯一的。用 class 查找的話,最好現(xiàn)在瀏覽器的網(wǎng)頁源碼中 Ctrl + F 搜索一下,相同 class 的標(biāo)簽有多少。(如果比較多的話,可以嘗試先查找他的父標(biāo)簽,縮小范圍之后再查找)然后我們再講講 find_all 函數(shù),適用于一次性查找一類型的很多標(biāo)簽的情況,
比如下圖這種情況:
列表中的每一個 li 標(biāo)簽中,都是一條數(shù)據(jù),我們需要將它們都獲取到,如果是用前面的 find 函數(shù)的話,每次只能獲取一個 li 標(biāo)簽。
所以我們需要使用 find_all 函數(shù),一次性獲取所有符合條件的標(biāo)簽,存儲為數(shù)組返回。
由于 li 標(biāo)簽沒有 id 也沒有 class ,而頁面中存在很多無關(guān)的干擾的 li 標(biāo)簽,所以我們需要先從它的父標(biāo)簽往上找,縮小查找范圍。
找到 id 為 titleList 的 div 標(biāo)簽之后,觀察一下,里面的 li 標(biāo)簽都是需要的,直接 find_all 函數(shù)一下都獲取完。
# html 是獲取的目標(biāo)網(wǎng)頁內(nèi)容 html = fetchUrl(pageUrl) bsobj = bs4.BeautifulSoup(html,'html.parser') pDiv = bsobj.find('div', attrs = {'id': 'titleList'}) titleList = pDiv.find_all('li')
基本上把 find 和 find_all 函數(shù)組合使用,用熟練
了可以應(yīng)付幾乎所有的 html 網(wǎng)頁了。
07 獲取標(biāo)簽中的數(shù)據(jù)
查找到標(biāo)簽之后,又該如何獲取標(biāo)簽中的數(shù)據(jù)呢?
標(biāo)簽中的數(shù)據(jù)位置,一般有兩種情況:
<!--第一種,位于標(biāo)簽內(nèi)容里--> <p>這是數(shù)據(jù)這是數(shù)據(jù)</p> <!--第二種,位于標(biāo)簽屬性里--> <a href="/xxx.xxx_xx_xx.html" rel="external nofollow" ></a>
如果是第一種情況很簡單,直接 pTip.text 即可;(pTip 是前面已經(jīng)獲取好的 p 標(biāo)簽)
如果是第二種情況,需要看它是在哪一個屬性里的數(shù)據(jù),比如我們要獲取上面 a 標(biāo)簽中的 href 屬性中的鏈接,可以 link = aTip["href"] 即可。(aTip 是前面已經(jīng)獲取好的 a 標(biāo)簽)。
08 去除指定內(nèi)容
去除獲取內(nèi)容首尾的指定字符串;
問題:
有時我們在爬蟲過程中,獲取到的信息并不是全部都是我們想要獲取的內(nèi)容,如果想要去除指定內(nèi)容。
例如:字符串 a = [“aaaa”],但是只想獲取中間的aaaa
解決:
可以通過使用正則表達式來提取文本內(nèi)容,去除符號,但如果使用此方法,字符串中間包含的符號也會被去掉。如果確定想要獲取的文本中間不含有符號,可以選用正則表達式。
x = strTemp.xpath('./div[3]/div[1]/div/div/div[1]/div/ul/li[14]//text()') xx = str(x) email = re.search(r'[\u4e00-\u9fa5a-zA-Z0-9]+', xx)
第二種方法是使用strip()方法來去除首尾的符號;
a = td[1].get_text().strip('[""]')
09 轉(zhuǎn)化為字符串類型
通過正則表達式獲取的內(nèi)容是match類型,如何轉(zhuǎn)化為字符串str類型?
解決辦法:使用group(0)來轉(zhuǎn)化為字符串類型
email = email.group(0)
10 濫用遍歷文檔樹
常見的方法有:
- contents
- descendants
- parent
- next_sibling
- next_element
這些方法都會遍歷文檔樹中的所有節(jié)點,包括文本節(jié)點。也就是說只要你使用這些方法,你就一定會選擇出許多文本節(jié)點,因為文本節(jié)點無處不在,換行、空格等。
解決方法:使用過濾器find等方法;
soup.find(name=‘tagname')
當(dāng)我們一旦在過濾器中指定了name關(guān)鍵字,那么返回的結(jié)果就一定是tag對象,因為文檔節(jié)點沒有name屬性。
結(jié)論:大多數(shù)情況下,你需要的是find 這一類過濾器,而不是遍歷所有節(jié)點。
11 數(shù)據(jù)庫保存問題
在將數(shù)據(jù)存儲到數(shù)據(jù)庫的過程中,遇到的一些問題如下:
1)爬取后的內(nèi)容是列表形式,如何轉(zhuǎn)化為字符串類型?
解決辦法:使用str()方法
b = strTemp.xpath('./div[3]/div[1]/div/div/div[1]/div/ul/li[3]//text()') agee = str(b)
注意:數(shù)據(jù)庫中對對應(yīng)的字段類型要與python中一致;
2)python 與 mysql 數(shù)據(jù)庫連接
connection = pymysql.connect(host='localhost', user='root', password='zaizaizai', db='layer', charset='utf8mb4') try: # 獲取會話指針 with connection.cursor() as cursor: # 創(chuàng)建SQL語句 sql = "insert into layer(namee,organization,sex,age,nation,edu,leixing,zhengzhi,numberr,first_time,get_time,status,paizhu,sifaju,email) values (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) " # 執(zhí)行SQL語句 cursor.execute(sql, (namee, organization, sex, age, nation, edu, leixing, zhengzhi, numberr, first_time, get_time, status, paizhu, sifaju, email)) # 提交 connection.commit() finally: connection.close()
3)保存在一個文件夾里的圖片不停的被后來抓取的圖片覆蓋掉?可能是變量放在for 循環(huán)內(nèi)外出現(xiàn)的問題,修改后解決此問題,用一些小例子做測試:
#測試一 x=0 for i in range(0,10): for j in range(0,10): print x x+=1 for i in range(0,10): x=0 for j in range(0,10): print x x+=1 #測試二 >>> x=0 >>> for i in range(0,10): print('the i is',i) x=0 for j in range(0,10): print('the j is',j) print('the x is',x) x+=1 >>> for i in range(0,10): print('the i is',i) x=0 for j in range(0,10): print('the j is',j) print('the x is',x) x=x+1
12 爬蟲采集遇到的墻問題
主要表現(xiàn)就是訪問不了了、訪問幾次就斷了,然后手動訪問,或者換個終端以后完全沒問題。?
面對這種情況,我們首先先想一下以下這幾個問題:
- 爬蟲是否過于頻繁訪問?影響了對方服務(wù)器的業(yè)務(wù)?
- 爬蟲采集是否觸犯了對方的防御機制?
- 爬蟲是否有bug導(dǎo)致對方不堪重負(fù)?
以上這些主觀問題是紅線問題,是要考慮到的,自己要把握好爬蟲的分寸,不能損壞他人合法利益??偨Y(jié)了一下遇到一些被管理員和防火墻攔截的問題和原因;
1)管理員不想讓你訪問,這種情況分兩種:
第一種:管理員登錄后臺一看,有個家伙10個小時就訪問了我的1萬次,不帶停的?。∪绻俏?,第一反應(yīng)就是封了再說,肯定有問題,都不用分析。
第二種:我感覺有人在爬數(shù)據(jù),我要把他找出來。
2)網(wǎng)站配置不讓你訪問這個主要是網(wǎng)站限制;比如tomcat或者Nginx,限制某個連接訪問時間,因為如果這個連接一直在這里,然后不斷有新的連接進來,那么連接池遲早滿了,最終完蛋。
你這就是要DDOS攻擊我,所以我可以設(shè)置多長時間你沒動靜,我就給你斷了。
3)防火墻配置
這個就難了,因為防火墻管的很細(xì),如果是服務(wù)器WAF那就很頭疼,他在攔截DDOS的時候,就非常容易把爬蟲也攔截掉。如果要繞過WAF,那就是一種入侵行為了,要從頁面采集數(shù)據(jù)就只能欺騙WAF,繼續(xù)刷新數(shù)據(jù)頁面。
4)如何避開以上策略,主要就是欺騙和偽裝。
逃避IP識別
通過采用或者構(gòu)建IP代理,變換IP地址,減少單IP對規(guī)則的觸發(fā),封禁惡意IP是網(wǎng)站管理過程中最常用的方式。
變換請求內(nèi)容
雖然你的IP變了,但還是這個瀏覽器、還是這個cookies記錄,也會被識別。這個可以通過變換cookies和header進行偽裝。
降低訪問頻率
一般如果單位時間內(nèi),訪問網(wǎng)站的次數(shù)過高,很容易被判斷為是CC攻擊。并且會對服務(wù)器帶來很大的壓力,影響正常業(yè)務(wù)。這就背離了我們進行數(shù)據(jù)采集的初衷了,所以設(shè)置一個sleep(),降低一下刷新頻率,減少一些對服務(wù)器資源的占用。
慢速攻擊判別
慢了也會被干掉的!?慢速攻擊是 http 慢速攻擊是利用http合法機制,在建立連接后盡量長時間保持連接,不釋放,達到對HTTP服務(wù)攻擊。攻擊者發(fā)送POST請求,自行構(gòu)造報文向服務(wù)器提交數(shù)據(jù),將報文長度設(shè)置一個很大的值。
且在隨后每次發(fā)送中,每次只發(fā)送一個很小的報文,這樣導(dǎo)致服務(wù)器一直等待數(shù)據(jù),連接始終一直被占用。如果攻擊者使用多線程或傀儡機子去做同樣操作,服務(wù)器WEB容器很快就被占滿TCP連接而不再接受新請求,從而導(dǎo)致服務(wù)器崩潰、服務(wù)失效。這個最好就是采用多線程異步采集,同時及時把之前的連接關(guān)閉,并且控制數(shù)量進行。
總之,在采集數(shù)據(jù)的時候,多站在別人的角度上思考,采用別人能接受的方式獲取數(shù)據(jù),別人才能讓你好好的獲取數(shù)據(jù)。這是一個雙贏的行為,不然就只能從入門到入牢的轉(zhuǎn)變了,大家都不容易,互相給口飯吃。
13 驗證碼問題
驗證碼的問題一般如何解決?大體的思路有兩種:
- 正向破解
- 逆向破解
正向破解
比如常見的圖形驗證碼,你可以首先把圖片保存下來,然后利用一些圖文識別圖去識別相應(yīng)的內(nèi)容。對于滑塊驗證碼,你可以利用 Selenium 框架去計算缺口的距離,然后模擬鼠標(biāo)拖動滑塊。
逆向破解
這個就涉及到驗證碼的實現(xiàn)邏輯,你需要看懂對方驗證碼實現(xiàn)的邏輯??纯窗l(fā)送驗證碼請求的時候需要哪些參數(shù),而這些參數(shù)又是怎么生成的,模擬請求。
逆向破解屬于短暫型的省力做法,但相應(yīng)的難度非常的大。-> 直接使用打碼平臺上面說兩種方式都屬于非常耗時耗力的行為,而且一旦對方網(wǎng)站反爬策略更新,你的代碼就會失效。所以能花錢解決的事,大家就選擇直接使用打碼平臺就好。?
學(xué)會Python爬蟲需要具備三部分的內(nèi)容:
- Python 基礎(chǔ)
- 爬蟲基礎(chǔ)
- 反爬的學(xué)習(xí)
這三部分內(nèi)容是做爬蟲的必備基礎(chǔ),主流語言是使用Python,Python有非常豐富的爬蟲庫可以直接使用,非常的方便。
到此這篇關(guān)于python爬蟲抓取時常見的小問題總結(jié)的文章就介紹到這了,更多相關(guān)python問題總結(jié)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Python實現(xiàn)普通圖片轉(zhuǎn)ico圖標(biāo)的方法詳解
ICO是一種圖標(biāo)文件格式,圖標(biāo)文件可以存儲單個圖案、多尺寸、多色板的圖標(biāo)文件。本文將利用Python實現(xiàn)普通圖片轉(zhuǎn)ico圖標(biāo),感興趣的小伙伴可以了解一下2022-11-11Python+uiautomator2實現(xiàn)自動刷抖音視頻功能
這篇文章主要介紹了Python+uiautomator2實現(xiàn)自動刷抖音視頻功能,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-04-04python wav模塊獲取采樣率 采樣點聲道量化位數(shù)(實例代碼)
這篇文章主要介紹了python wav模塊獲取采樣率 采樣點聲道量化位數(shù),本文通過實例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價值,需要的朋友可以參考下2020-01-01