爬蟲入門四 re
title: 爬蟲入門四 re
date: 2020-03-14 16:49:00
categories: python
tags: crawler
正則表示式與re庫
1 正則表示式簡介
編譯原理學過的
正則表示式(Regular Expression,簡寫為regex或RE),使用單個字串來描述、匹配一系列匹配某個句法規則的字串。在很多文字編輯器裡,正則表示式通常被用來檢索、替換那些匹配某個模式的文字。
Python 中匯入 :import re
官方參考文件:https://docs.python.org/3/library/re.html
正則表示式元素可以歸為字元和操作符,操作符又包含了限定符和定位符
字元: 字元可以代表一個單獨的字元,或者一個字元集合構成的字串。
限定符:允許你在模式中決定字元或者字串出現的頻率。
定位符:允許你決定模式是否是一個獨立的單詞,或者出現的位置必須在句
子的開頭還是結尾。
http://www.cnblogs.com/huxi/archive/2010/07/04/1771073.html
1.1 正則表示式 常用操作符
. 表示任何單個字元 [] 字符集,對單個字元給出取值範圍 [abc]表示a、b、c, [a-z ]表示a到z單個字元 [\d\.] 表示包含數字和小數點 [^ ] 非字符集,對單個字元給出排除範圍 [^abc ]表示非a或b或c的單個字元 * 前一個字元0次或無限次擴充套件 abc*表示ab、abc、abcc. abccc等 + 前一個字元1次或無限次擴充套件 abc+表示abc、abcc、 abccc等 ? 前一個字元0次或1次擴充套件 abc?表示ab、abc | 左右表示式任意-個 abc|def表示abc、 def {m} 擴充套件前一個字元m次 ab{2}c表示abbc {m,n} 擴充套件前一個字元m至n次(含n) ab{1, 2}c表示abC、abbc ^ 匹配字串開頭 ^abc表示abc且在一個字串的開頭 $ 匹配字串結尾 abc$表示abc且在一個字串的結尾 () 分組標記,內部只能使用|操作符 (abc )表示abc , (abc |def)表示abc、def \d 數字,等價於[0-9] \w 單詞字元,等價於[A-Za-z0-9]
" 表示轉義 "。比如 下面例項淘寶中price":"412.00"
r'"view_price":"[\d.]*"'
[\d.] 表示包含數字和小數點
1.2 re例項
正則表示式線上檢查網站:
http://tool.oschina.net/regex/
email地址:
只允許英文字母、數字、下劃線、英文句號、以及中劃線組成
[1]+@[a-zA-Z0-9_-]+(.[a-zA-Z0-9_-]+)+$
PY[^TH]?ON 'PYON'、'PYaON'、 ' PYbON'、'PYcON'... PY{ :3}N 'PN'、 'PYN'、 'PYYN'、 'PYYYN'... ^[A-Za-z]+$ 由26個字母組成的字串 ^[A-Za-z0-9]+$ 由26個字母和數字組成的字串 ^-?\d+$ 整數形式的字串 ^[0-9]*[1-9][0-9]*$ 正整數形式的字串 [1-9]\d{5} 首位不為0 中國境內郵政編碼, 6位 [\u4e00-\u9fa5] 匹配中文字元 \d{3}-\d{8}|\d{4}-\d{7} 國內電話號碼, 010-68913536
IP地址字串形式的正則表示式( IP地址分4段,每段0-255 )
\d+.\d+.\d+.\d+或\d{1,3}. \d{1,3}.\d{1,3}. \d{1,3}
精確寫法
0-99: [1-9]?\d
100-199: 1\d{2}
200-249: 2[0-4]\d
250-255: 25[0-5]
(([1-9]?\d|1\d{2}|2[0-4]\d|25[0-5]).){3}([1-9]?\d|1\d{2}|2[0-4]\d|25[0-5])
2 re庫
2.1 re庫中正則式的使用
re庫採用raw string(原生字串)型別表示正則表示式,表示為:
r'text'
例如: r'[1‐9]\d{5}'
r'\d{3}‐\d{8}|\d{4}‐\d{7}'
>>> import re
>>> m=re.search(r'[0-9]','wu1han2')
>>> print(m.group(0))
可以得到返回結果:1
2.2 re庫常用功能函式
re. search( )
在一個字串中搜索匹配正則表示式的第一個位置,返回match物件
re. match( )
從一個字串的開始位置起匹配正則表示式,返回match物件
re. findall()
搜尋字串,以列表型別返回全部能匹配的子串
re. split( )
將一個字串按照正則表示式匹配結果進行分割,返回列表型別
re. finditer( )
搜尋字串,返回-一個匹配結果的迭代型別,每個迭代元素是match物件
re.sub( )
在一個字串中替換所有匹配正則表示式的子串,返回替換後的字串
2.2.1 re.search()
re.search(pattern, string, flags=0)
在一個字串中搜索匹配正則表示式的第一個位置
pattern : 正則表示式的字串或原生字串表示
string : 待匹配字串
flags : 正則表示式使用時的控制標記
re.I re. IGNORECASE
忽略正則表示式的大小寫, [A- Z]能夠匹配小寫字元
re.M re . MULTILINE
正則表示式中的^操作符能夠將給定字串的每行當作匹配開始
re.S re. DOTALL
正則表示式中的.操作符能夠匹配所有字元,預設匹配除換行外的所有字元
2.2.2 re.match()
re.match(pattern, string, flags=0)
從一個字串的開始位置起匹配正則表示式
import re
search=re.search(r'[1-9]\d{5}','WuHan 430073')
match1=re.match(r'[1-9]\d{5}','WuHan 430073')
match2=re.match(r'[1-9]\d{5}','430073 WuHan')
print("search結果")
if search:
print(search.group(0))
print("\nmatch1結果")
if match1:
print(match1.group(0))
else:
print("未匹配到結果")
print("\nmatch2結果")
if match2:
print(match2.group(0))
上面輸出
430073
未匹配
430073
2.2.3 re.findall .finditer
re.findall(pattern, string, flags=0)
搜尋字串,以列表型別返回全部能匹配的子串
re.finditer(pattern, string, flags=0)
搜尋字串,返回一個匹配結果的迭代型別,每個結果為match物件
import re
findall=re.findall(r'[1-9]\d{5}','WuHan 430073 Beijing 100010')
print("findall匹配結果")
if findall:
print(findall)
print("finditer匹配結果")
for m in re.finditer(r'[1-9]\d{5}','WuHan 430073 Beijing 100010'):
if m:
print(m.group(0))
# 輸出
[430073,100010]
430073
100010
2.2.4 re.split()
re.split(pattern, string, maxsplit=0,flags=0)
將一個字串按照正則表示式匹配結果進行分割,返回列表型別
pattern : 正則表示式的字串或原生字串表示
string : 待匹配字串
maxsplit: 最大分割數,剩餘部分作為最後一個元素輸出
flags : 正則表示式使用時的控制標記
就是把pattern當做分隔符
import re
split1=re.split(r'[1-9]\d{5}','WuHan 430073 Beijing 100010')
split2=re.split(r'[1-9]\d{5}','WuHan 430073 Beijing 100010',maxsplit=1)
print("split1分割結果")
print(split1)
print("split2分割結果")
print(split2)
split1分割結果
['WuHan ', ' Beijing ', '']
split2分割結果
['WuHan ', ' Beijing 100010']
2.2.5 re.sub
re.sub(pattern, repl, string, count=0, flags=0)
在一個字串中替換所有匹配正則表示式的子串返回替換後的字串
pattern : 正則表示式的字串或原生字串表示
repl : 替換匹配字串的字串
string : 待匹配字串
count : 匹配的最大替換次數
flags : 正則表示式使用時的控制標記
sub = re.sub(r'[1-9]\d{5}', '123456', 'WuHan 430073 Beijing 100010')
print("替換結果")
print(sub)
替換結果
WuHan 123456 Beijing 123456
2.3 re庫面向物件用法
regex = re.compile(pattern, flags=0)
pattern : 正則表示式的字串或原生字串表示
flags : 正則表示式使用時的控制標記
regex = re.compile(r‘[1‐9]\d{5}’)
將正則表示式的字串形式編譯成正則表示式物件,編譯後,可進行多次操作
如下,就相當於用pat代表了這個re,方便了書寫
pat = re.compile(r'[1-9]\d{5}')
a = pat.search('wuhan430073')
print("搜尋結果")
print(a)
b = pat.sub('...','bj100010 wh430073')
print("替換結果")
print(b)
搜尋結果
<re.Match object; span=(5, 11), match='430073'>
替換結果
bj... wh...
這裡regex就算自定義的物件,如上面的pat.search()
regex. search( )
在一個字串中搜索匹配正則表示式的第一個位置,返回match物件
regex. match( )
從一個字串的開始位置起匹配正則表示式,返回match物件
regex . findall()
搜尋字串,以列表型別返回全部能匹配的子串
regex.split()
將一個字串按照正則表示式匹配結果進行分割,返回列表型別
regex. finditer()
搜尋字串,返回一個匹配結果的迭代型別,每個迭代元素是match物件
regex. sub( )
在一個字串中替換所有匹配正則表示式的子串,返回替換後的字串
2.4 re庫的match物件
match物件包含了關於此次匹配的資訊,可以使用Match提供的可讀屬性或方法來獲取這些資訊。
.string
待匹配的文字
.re
匹配時使用的patter物件(正則表示式)
.pos
正則表示式搜尋文字的開始位置 就是待匹配串從哪裡開始搜尋
.endpos
正則表示式搜尋文字的結束位置 就是待匹配串從哪裡借結束搜尋,比如19,說明搜尋到18(含18)
.group(0)
獲得匹配後的字串
.start()
匹配字串在原始字串的開始位置
.end()
匹配字串在原始字串的結束位置
.span()
返回(.start(), .end()
for m in re.finditer(r'[1-9]\d{5}', 'WuHan 430073 Beijing 100010'):
if m:
print(m.group(0))
print(m.pos)
print(m.endpos)
print(m.start())
print(m.end())
print(m.span())
123456
430073
0
28
6
12
(6, 12)
100010
0
28
22
28
(22, 28)
2.5 re庫的貪婪匹配,最小匹配
match = re. search(r'PY.*N', ' PYANBNCNDN' )
match. group(0)
同時匹配長短不同的多項,返回哪個呢?(PYAN PYANBN PYANBNCN ...)
結果:
'PYANBNCNDN'
Re庫預設採用貪婪匹配,即輸出匹配最長的子串
如何輸出最短的子串呢?
match = re. search(r'PY.*?N', ' PYANBNCNDN' )
match. group(0)
'PYAN '
只要長度輸出可能不同的,都可以通過在操作符後增加?變成最小匹配
*? 前一個字元0次或無限次擴充套件,最小匹配
+? 前一個字元1次或無限次擴充套件,最小匹配
?? 前一個字元0次或1次擴充套件,最小匹配
{m,n}? 擴充套件前一個字元m至n次(含n),最小匹配
3 例項一 淘寶商品定向爬蟲
3.1 url的確定
第二頁:
https://s.taobao.com/search?q=鍵盤&imgfile=&commend=all&ssid=s5-e&search_type=item&sourceId=tb.index&spm=a21bo.2017.201856-taobao-item.1&ie=utf8&initiative_id=tbindexz_20170306&bcoffset=3&ntoffset=3&p4ppushleft=1%2C48&s=44
第三頁:
https://s.taobao.com/search?q=鍵盤&imgfile=&commend=all&ssid=s5-e&search_type=item&sourceId=tb.index&spm=a21bo.2017.201856-taobao-item.1&ie=utf8&initiative_id=tbindexz_20170306&bcoffset=0&ntoffset=6&p4ppushleft=1%2C48&s=88
觀察發現:
每頁44個商品。
然後
&bcoffset=3&ntoffset=3&p4ppushleft=1%2C48
&imgfile=&commend=all&ssid=s5-e&search_type=item&sourceId=tb.index&spm=a21bo.2017.201856-taobao-item.1&ie=utf8&initiative_id=tbindexz_20170306
這些資訊不用,比如
https://s.taobao.com/search?q=鍵盤&s=44
%E9%94%AE%E7%9B%98 是搜尋的物品。
就可以訪問第二頁
這裡我不清楚,可能是是這些是類似時間戳一類的資訊,呼叫的引數等待??,不明白
第一頁可以直接https://s.taobao.com/search?q='goods'訪問。
所以url的有效部分就是
https://s.taobao.com/search?q= &s=
https://s.taobao.com/search?q='goods'&s='str(44 * i)'
goods = '鍵盤'
depth = 3
start_url = 'https://s.taobao.com/search?q='+ goods
infoList = []
for i in range(depth):
try:
url = start_url + '&s=' + str(44 * i)
3.2 商品價格和名稱
然後找到商品和價格格式
這裡用檢視原始碼,而不是檢查搜尋一款鍵盤的價格//名字
"raw_title":"黑爵AK35i機械鍵盤青軸黑軸茶軸吃雞巨集電競"
"view_price":"812.42"
可見格式,用re表達
plt = re.findall(r'"view_price":"[\d.]"', html)
tlt = re.findall(r'"raw_title":".?"', html)
然後,對搜尋結果進行分割,以“:”為分割點,價格和格式,為分割點後的內容
新增至列表中
for i in range(len(plt)):
price = eval(plt[i].split(':')[1])
title = eval(tlt[i].split(':')[1])
ilt.append([price, title])
eval():將字串當成有效的表示式來求值並返回計算結果
增加eval()後,返回結果無引號
"99.12"->99.12
3.3 code
步驟1:提交商品搜尋請求,迴圈獲取頁面
getHTMLText()
步驟2:對於每個頁面,提取商品名稱和價格資訊
parsePage()
步驟3:將資訊輸出到螢幕上
printGoodsList()
# 淘寶商品定向爬蟲,序號,價格,名稱
# 問題是在getHTMLText的時候需要登入。待解決。(用cookie,request.session更新cookie?)
import requests
import re
def getHTMLText(url):
try:
r = requests.get(url, timeout=30)
r.raise_for_status()
r.encoding = r.apparent_encoding
#print(r.text)
return r.text
except:
print("gethtmlfailed")
return ""
def parsePage(ilt, html):
try:
plt = re.findall(r'\"view_price\"\:\"[\d\.]*\"', html)
tlt = re.findall(r'\"raw_title\"\:\".*?\"', html)
for i in range(len(plt)):
price = eval(plt[i].split(':')[1])
title = eval(tlt[i].split(':')[1])
ilt.append([price, title])
except:
print("parsePagefailed")
def printGoodsList(ilt):
tplt = "{:4}\t{:8}\t{:16}"
print(tplt.format("序號", "價格", "商品名稱"))
count = 0
for g in ilt:
count = count + 1
print(tplt.format(count, g[0], g[1]))
def main():
goods ="鍵盤"
depth = 3
start_url = 'https://s.taobao.com/search?q='+ goods
infoList = []
for i in range(depth):
try:
url = start_url + '&s=' + str(44 * i)
html = getHTMLText(url)
parsePage(infoList,html)
except:
continue
printGoodsList(infoList)
if __name__=='__main__':
main()
3.4 問題
跑不出來,發現淘寶需要登入。
網路上有一些模擬登陸的方法。
需要request.session
4 例項二 股票資訊定向爬取 找尋爬取網址
4.1 問題
例項程式碼不知道是什麼時候的了,股市通web版都倒了
下面的內容看看就好
4.2 視覺化進度,處理個別情況
在爬取到個股資訊後,需要將其寫入檔案中,
with open(fpath, 'a', encoding='utf-8') as f:
f.write(str(infoDict) + '\n')
由於爬取資料量大,爬取速度較慢,為了方便使用者看到爬取進度,可以定義全域性變數count,每爬取一支個股後count=count+1 。總進度為count除以list的長度。
為了顯示更新效果,可以在print內容中加入”\r”
使新列印的一行,沖掉之前舊的一行,達到顯示進度不斷增大的效果。
count = count + 1
print("\r當前進度: {:.2f}%".format(count * 100 / len(lst)), end="")
同時,使用try:
***
except:
continue
的結構,使個別爬取結果空時不會影響總的爬取過程
4.3 編碼加速
在獲取HTML時,用到了r.encoding= r.apparent_encoding
r.apparent_encoding需要分析文字,因此執行較慢
因此,當已知目標網頁的編碼的方式時,可以直接定義code=“編碼方式“
令r.encoding = code
通過檢視東方財富網的原始碼,
head中獲取到它的編碼方式為“gb2312”
而百度股市通編碼方式為“utf-8”
優化後爬取速度仍然較慢,因此,當爬取較大資料量的網頁資訊時,可採取多執行緒技術、或使用更適合大規模資料爬取的scrapy框架
4.4 股票標號
檢視東方財富網網頁原始碼,可以看到,股票標號可以在a標籤的href值中找到
用BeautifulSoup的find_all方法找到所有a標籤,得到href中的內容,
再使用正則表示式,找到:
以s開頭,第二個字母為h或者z之後是六位數字 // sh 上海 sz 深圳
正則表示式為:r"[s][hz]\d{6}“
的物件加入到lst中
4.5 updata split
根據股票列表逐個到百度股票獲取個股資訊
for stock in lst:
url = stockURL + stock + ".html"
html = getHTMLText(url)
檢視個股網頁原始碼
可以看出,股票資訊都在class為stock-bets的
div標籤下,將其存入stockinfo:
stockInfo = soup.find('div', attrs={'class': 'stock-bets'})
股票名儲存在class為bets-name的
name = stockInfo.find_all(attrs={'class': 'bets-name'})[0]
infoDict.update({'股票名稱': name.text.split()[0]})
update()方法語法:dict.update(dict2),把字典dict2的鍵/值對更新到dict裡。
split():當不帶引數時以空格進行分割,當代引數時,以該引數進行分割。
4.6 key-value
股票當日數值均在
- 標籤的
- 標籤中,而這些數值的含義,均在
- 標籤的
- 標籤中。
因此,可以構建多組鍵值對:
keyList = stockInfo.find_all(‘dt’) #鍵
valueList = stockInfo.find_all(‘dd’) #值
for i in range(len(keyList)):
key = keyList[i].text
val = valueList[i].text
infoDict[key] = val #將這對鍵值對存入字典中4.7 code
#CrawBaiduStocksB.py # 這個例子不知道是啥時候的了,股市通web都倒了(哭腔) #可以換成同花順? import requests from bs4 import BeautifulSoup import traceback import re def getHTMLText(url, code="utf-8"): try: r = requests.get(url) r.raise_for_status() r.encoding = code return r.text except: return "" def getStockList(lst, stockURL): html = getHTMLText(stockURL, "GB2312") soup = BeautifulSoup(html, 'html.parser') a = soup.find_all('a') for i in a: try: href = i.attrs['href'] lst.append(re.findall(r"[s][hz]\d{6}", href)[0]) except: continue def getStockInfo(lst, stockURL, fpath): count = 0 #進度 for stock in lst: url = stockURL + stock + ".html" html = getHTMLText(url) try: if html == "": continue infoDict = {} soup = BeautifulSoup(html, 'html.parser') stockInfo = soup.find('div', attrs={'class': 'stock-bets'}) name = stockInfo.find_all(attrs={'class': 'bets-name'})[0] infoDict.update({'股票名稱': name.text.split()[0]}) keyList = stockInfo.find_all('dt') valueList = stockInfo.find_all('dd') for i in range(len(keyList)): key = keyList[i].text val = valueList[i].text infoDict[key] = val with open(fpath, 'a', encoding='utf-8') as f: f.write(str(infoDict) + '\n') count = count + 1 print("\r當前進度: {:.2f}%".format(count * 100 / len(lst))) except: count = count + 1 print("\r當前進度: {:.2f}%".format(count * 100 / len(lst))) continue def main(): stock_list_url = 'http://quote.eastmoney.com/stocklist.html' stock_info_url = 'https://gupiao.baidu.com/stock/' output_file = 'D:/BaiduStockInfo.txt' slist = [] getStockList(slist, stock_list_url) getStockInfo(slist, stock_info_url, output_file) main()
a-zA-Z0-9_- ↩︎
- 標籤中。