〇欢策、整個公司被抓
2019 年的某一個工作日,公司員工像往常一樣忙忙碌碌赏淌,某個程序員和產(chǎn)品經(jīng)理正在為了一個需求爭吵踩寇,小明帶著耳機正坐在辦公室敲代碼。
突然就來了一大群警察六水,要求所有人離開工位俺孙,雙手離開電腦、手機等設(shè)備掷贾。整個公司的人都懵了睛榄,不知道發(fā)生了什么事情,但也都照辦了想帅。
警察很快查封了公司的所有辦公用品场靴,問技術(shù)部相關(guān)人員要了服務(wù)器的信息,公司全體上下 200 多人無差別的全部送到看守所了解情況港准。
在去看守所的路上旨剥,大家都還心里想這是不是搞錯了,我們只是一個科技公司公司又沒有騙人浅缸,怎么就集體被抓了轨帜。
小明也一直認為自己沒有犯罪,自己只是一名技術(shù)人員而已衩椒,所有的工作也都是按照領(lǐng)導(dǎo)要求來執(zhí)行的蚌父,應(yīng)該很快就會把我們釋放了吧。
隨后毛萌,公司非核心人員都被釋放了出來梢什,主要集中在 HR、行政人員朝聋。最后確認公司 36 人被捕嗡午,其中大部分是程序員。
被捕后小明委托的律師事務(wù)所冀痕,就是和我們交流的兩位律師的事務(wù)所荔睹,據(jù)說小明入獄后就一直不認為自己有罪,也因一直拒絕認罪從而錯過了取保候?qū)彽臋C會言蛇。
目前小明還在等待最后的審判……
好了僻他,下面我們開始學 坐牢 爬蟲。
一腊尚、什么是爬蟲
其實你身邊到處都是爬蟲的產(chǎn)物吨拗,比如說搜索引擎 Google、百度,它們能為你提供這么多搜索結(jié)果劝篷,也都是因為它們或取了很多網(wǎng)頁信息哨鸭,然后展示給你。再來說一些商業(yè)爬蟲娇妓,比如爬取淘寶的同類商品的價格信息像鸡,好為自己的商品挑選合適的價格。爬蟲的用途很多很多哈恰,網(wǎng)上的信息成百上千只估,只要你懂爬蟲,你都能輕松獲取着绷。
二蛔钙、爬取網(wǎng)頁
這里使用 Python3 來做爬蟲,首先你得下載 Python3荠医,如果你還沒有安裝可以看一看《Linux 安裝 Python 3.x》夸楣。
然后還需要安裝 Requests,這是一個 Python 的常用的外部模塊子漩,我們需要手動安裝它豫喧。簡單的方法,使用 pip 安裝就好了幢泼。
$ pip3 install requests
現(xiàn)在我們已經(jīng)可以開始爬取網(wǎng)頁了紧显。例如,我們爬取百度百科
import requests
URL = 'https://baike.baidu.com/item/%E7%BD%91%E7%BB%9C%E7%88%AC%E8%99%AB'
# 使用 requests 發(fā)起 Get 請求獲取網(wǎng)頁數(shù)據(jù)
request = requests.get(URL)
# 使用 utf-8 編碼缕棵,避免亂碼
request.encoding = 'utf-8'
# 獲取請求文本
html = request.text
print(html)
經(jīng)過這么幾行代碼孵班,我們已經(jīng)把百度百科的一個網(wǎng)頁的信息全部爬取下來了。
好招驴,我們換一個 URL 爬取試試篙程,比如說簡書:http://www.reibang.com/u/5fa5459c7b02
。這時你會發(fā)現(xiàn)出問題了别厘。
出現(xiàn)了 403 Forbidden虱饿,訪問拒絕。這是因為簡書發(fā)現(xiàn)你可能是通過爬蟲來訪問頁面了触趴,所以會出現(xiàn)拒絕氮发。怎么辦呢?
我們可以使用偽裝冗懦,就是偽裝自己是瀏覽器爽冕,打開瀏覽器控制臺,查看請求披蕉,可以在請求頭中看到 user-agent
這一段:
我們也仿照瀏覽器的請求颈畸,也就是在請求頭中加入 user-agent
乌奇,完整代碼如下:
import requests
URL = 'http://www.reibang.com/u/5fa5459c7b02'
# 請求頭
headers = {'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.79 Safari/537.36'}
# 使用 requests 發(fā)起 Get 請求獲取網(wǎng)頁數(shù)據(jù)
request = requests.get(URL, headers=headers)
# 使用 utf-8 編碼,避免亂碼
request.encoding = 'utf-8'
# 獲取請求文本
html = request.text
print(html)
現(xiàn)在可以成功爬取了眯娱。
加入請求頭只是一種簡單的破解反爬技術(shù)礁苗,實際可能會遇到其他反爬取技術(shù),這就需要具體問題具體分析了困乒,比如請求帶 cookie
等寂屏。當然加請求頭的方法已經(jīng)可以成功爬取大部分網(wǎng)頁了贰谣。
目前很多網(wǎng)站都增加了頻率限制娜搂,一個 IP 在一定的時間內(nèi)訪問次數(shù)有限,最好不要去破解吱抚,一旦你的爬取頻率過高百宇,導(dǎo)致對方服務(wù)器癱瘓,這就是網(wǎng)絡(luò)攻擊了秘豹。
三携御、獲取需要的信息
雖然把網(wǎng)頁爬取下來了,但是有很多信息是我們不需要的既绕。例如啄刹,我只想看看網(wǎng)頁的標題,卻把整個網(wǎng)頁都爬取下來了凄贩。那怎么從整個網(wǎng)頁中獲取我們需要的內(nèi)容呢誓军?
如果對正則表達式很熟悉,完全可以使用正則表達式疲扎,過濾自己需要的內(nèi)容昵时;如果不熟悉還可以使用 BeautifulSoup 來幫我們解析網(wǎng)頁。
BeautifulSoup 是一個網(wǎng)頁解析的工具椒丧,有了這個工具壹甥,就可以省去大量正則表達式,簡化爬蟲代碼壶熏,快速獲取我們需要的內(nèi)容句柠。還可以查看 Beautiful Soup 文檔了解基本使用。
先安裝 BeautifulSoup:
$ pip3 install beautifulsoup4
接著再裝一個 HTML 解析器就好了:
$ pip3 install lxml
準備完畢棒假,可以開始了俄占。例如我需要獲取網(wǎng)頁的標題,獲取 <p> 標簽淆衷,獲取 <a> 標簽的連接等缸榄。
from bs4 import BeautifulSoup
import requests
URL = 'http://www.reibang.com/u/5fa5459c7b02'
headers = {'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.79 Safari/537.36'}
# 使用 requests 發(fā)起 Get 請求獲取網(wǎng)頁數(shù)據(jù)
request = requests.get(URL, headers=headers)
request.encoding = 'utf-8'
html = request.text
# 使用 BeautifulSoup 解析網(wǎng)頁
soup = BeautifulSoup(html, 'lxml')
# 獲取網(wǎng)頁 title
title = soup.title.string
# 獲取 <p> 標簽
p = soup.find_all('p')
# 獲取 <a> 標簽
a = soup.find_all('a')
# 獲取 <a> 標簽的鏈接
# 因為 <a> 真正的 link 在 <a href="link"> 里面,href 也可以看做是 <a> 的一個屬性祝拯,用字典的形式來讀取
a_href = [h['href'] for h in a]
# 獲取 class 為 'have-img' 的 <li> 標簽
img_li = soup.find_all('li', {'class': 'have-img'})
# 獲取 <li> 標簽里面的 <img> 標簽
for li in img_li:
imgs = li.find_all('img')
到這里甚带,已經(jīng)小功告成她肯,爬蟲的基本使用已經(jīng)介紹完畢,如果想爬取更符合自定義標準的信息鹰贵,那么還得需要了解正則表達式才行晴氨。
四、正則表達式
正則表達式 (Regular Expression) 又稱 RegEx碉输,是用來匹配字符的一種工具籽前。在一大串字符中尋找你需要的內(nèi)容。它常被用在很多方面敷钾,比如網(wǎng)頁爬蟲枝哄,文稿整理,數(shù)據(jù)篩選等等阻荒。最簡單的一個例子:
<td>
<img src="https://xxx/1.jpg">
<img src="http://xxx/2.jpg">
<img src="ftp://xxx/3.png">
</td>
我們只需要提取以 https 開頭的 url挠锥,這該如何處理呢?這是我們就需要用到正則表達式了侨赡。
正則表達式的大致匹配過程是:依次拿出表達式和文本中的字符比較蓖租,如果每一個字符都能匹配,則匹配成功羊壹;一旦有匹配不成功的字符則匹配失敗蓖宦。下面列出了正則表達式元字符和語法:
-
\d
:任何數(shù)字,[0-9] -
\D
:不是數(shù)字油猫,[^\d] -
\s
:任何空白字符, 如 [\t \n \r <空格>] -
\S
:不是空白字符 -
\w
:任何大小寫字母稠茂、數(shù)字,[a-zA-Z0-9] -
\W
:不是字母眨攘、數(shù)字主慰,[^\w] -
.
:匹配任何字符 (除了 \n) -
^
:匹配開頭 -
$
:匹配結(jié)尾 -
?
:匹配前一個字符0次或1次 -
*
:匹配前一個字符0次或多次 -
+
:匹配前一個字符1次或多次
下面舉例說明:
import re # 導(dǎo)入正則匹配模塊
# 匹配前一個字符1次或多次
re.search(r"Mon(day)+", "Monday") # <re.Match object; span=(0, 6), match='Monday'>
re.search(r"Mon(day)+", "Mon") # None
re.search(r"Mon(day)*", "Mon") # <re.Match object; span=(0, 3), match='Mon'>
# 匹配數(shù)字
re.compile('\d').match('abc123') # <re.Match object; span=(3, 4), match='1'>
re.compile('\d+').match('abc123') # <re.Match object; span=(3, 6), match='123'>
# 匹配字母和數(shù)字
re.compile('\w').match('abc123') # <re.Match object; span=(0, 1), match='a'>
re.compile('\w+').match('abc123') # <re.Match object; span=(0, 6), match='abc123'>
# 匹配開頭
re.compile('^abc').match('abc123') # <re.Match object; span=(0, 3), match='abc'>
# 匹配結(jié)尾
re.compile('123$').match('abc123') # <re.Match object; span=(3, 6), match='123'>
通過這些簡單的了解,我們就能知道以 https:// 開頭的正則表達式應(yīng)該怎么寫了:
re.compile('^(https://).+')
其中 ^(https://)
表示以 https:// 開頭鲫售,.
匹配任意字符共螺,+
表示匹配任意次數(shù),合并起來表示匹配以 https:// 開頭情竹,之后可以是任意字符出現(xiàn)任意次藐不。完整代碼如下:
from bs4 import BeautifulSoup
import requests
import re # 導(dǎo)入正則匹配模塊
# 獲取網(wǎng)頁
html = requests.get(URL).text
# 使用 BeautifulSoup 解析網(wǎng)頁
soup = BeautifulSoup(html, 'lxml')
# 獲取 <img> 標簽,并且 src 以 https:// 開頭
imgs = soup.find_all('img', {'src': re.compile('^(https://).*')})
print(imge) # <img src="https://xxx/1.jpg">
print(imge['src']) # https://xxx/1.jpg
如果需要獲取以 png 結(jié)尾的圖片呢秦效?只需要改一句話:
img = soup.find_all('img', {'src': re.compile('.+(.png)$')})
五雏蛮、圖片下載
現(xiàn)在我們來做一個小練習,就是爬取 國家地理中文網(wǎng) 中一個頁面上的所有圖片阱州,并且下載到本地挑秉。
可以看到,圖片是 <img> 標簽苔货,并且連接是以 http 開頭犀概,通過這樣簡單的分析立哑,就可以開始下載圖片了。
由于網(wǎng)頁可能會更新姻灶,代碼并不是一成不變的铛绰,重點是了解下載方法,同時也可以根據(jù)自己的需求去下載产喉。
from bs4 import BeautifulSoup
import requests
import re
import ssl
import urllib
# 解決訪問 HTTPS 時不受信任 SSL 證書問題
# ssl._create_default_https_context = ssl._create_unverified_context
URL = "http://www.nationalgeographic.com.cn/animals"
headers = {'User-Agent': 'Mozilla/5.0 3578.98 Safari/537.36'}
request = requests.get(URL, headers=headers)
request.encoding = 'utf-8'
html = request.text
soup = BeautifulSoup(html, 'lxml')
# 獲取 'http://' 或 'https://' 開頭的 <img> 標簽
img_urls = soup.find_all('img', {'src': re.compile('^(http(s)?://).+')})
for i in range(len(img_urls)):
url = img_urls[i]['src']
# 下載圖片
urllib.request.urlretrieve(url, '/Users/Desktop/Picture/' + str(i) + '.jpg')
圖片已經(jīng)下載完成
現(xiàn)在可以爬取一個網(wǎng)頁上的圖片了捂掰,如果我想把整個 國家地理中文網(wǎng) 上所有的圖片都下載呢?
import time
from bs4 import BeautifulSoup
import re
import requests
import urllib
import ssl
ssl._create_default_https_context = ssl._create_unverified_context
base_url = 'http://www.ngchina.com.cn/animals/'
headers = {'User-Agent': 'Mozilla/5.0 3578.98 Safari/537.36'}
def crawl(url):
'''
爬取網(wǎng)頁
:param url: url
:return: url and current web content
'''
request = requests.get(url, headers=headers)
request.encoding = 'utf-8'
html = request.text
return url, html
def parse(url, html):
'''
解析網(wǎng)頁
'''
soup = BeautifulSoup(html, 'lxml')
title = soup.title.string # 獲取網(wǎng)頁標題
# 獲取當前頁面上的鏈接曾沈,待下個循環(huán)爬取这嚣,只拿國家地理中文網(wǎng)的鏈接
urls = soup.find_all('a', {"href": re.compile('^(http://www.ngchina).+')})
page_urls = set([url['href'] for url in urls]) # 鏈接去重
download_img(soup) # 下載圖片
return title, url, page_urls
def download_img(soup):
"""
下載以 http 或 https 開頭的圖片
"""
img_urls = soup.find_all('img', {'src': re.compile('^(http(s)?://).+')})
for i in range(len(img_urls)):
url = img_urls[i]['src']
urllib.request.urlretrieve(url, '/Users/terry-jri/Desktop/Picture/' + str(time.time()) + '.jpg')
# 待爬取網(wǎng)頁url
unseen = set([base_url, ])
# 已爬取網(wǎng)頁url
seen = set()
count, t1 = 1, time.time()
while len(unseen) != 0: # still get some url to visit
if len(seen) > 100: # max count
break
print('\nCrawling...')
htmls = [crawl(url) for url in unseen]
print('\nParsing...')
results = [parse(url, html) for url, html in htmls]
print('\nAnalysing...')
seen.update(unseen) # seen the crawled
unseen.clear() # nothing unseen
for title, url, page_urls in results:
print(count, title, url)
count += 1
unseen.update(page_urls - seen) # get new url to crawl
print('Total time: %.1f s' % (time.time() - t1,))