引言:html
上一節學習了一波urllib庫和BeautifulSoup的使用,爬取不少小網站 基本是駕輕就熟的了,而通常咱們想爬取的數據基本都是字符串,圖片url, 或者是段落文字等,掌握字符串的處理顯得尤其重要,說到字符串處理, 除了瞭解字符串相關的處理函數外,還須要 正則表達式 這枚字符串處理神器! 對於正則表達式,不少人開發者貌似都很抗拒,老說學來幹嗎,要什麼 正則表達式上網一搜就是啦,對此我只能說2333,爬取網頁數據的時候, 你搜下給我看,不一樣的場景匹配字符串的正則表達式都是不同的,掌握 正則表達式的編寫就顯得尤其重要了。本節經過一些有趣的例子幫你 快速上手正則表達式,其實真沒想象中那麼難!python
Python中經過**re模塊
**使用正則表達式,該模塊提供的幾個經常使用方法:正則表達式
re.match
(pattern, string, flags=0)api
<class '_sre.SRE_Match'>
group與groupsre.search
(pattern, string, flags=0)bash
注意:match方法和search的最大區別:match若是開頭就不和正則表達式匹配, 直接返回None,而search則是匹配整個字符串!!app
re.findall
(pattern, string, flags=0)框架
re.finditer
(pattern, string, flags=0)scrapy
re.sub
(pattern, repl, string, count=0, flags=0)ide
re.split
(pattern, string, maxsplit=0, flags=0)函數
對於會屢次用到的正則表達式,咱們能夠調用re的compile()方法編譯成 Pattern對象,調用的時候直接Pattern對象.xxx便可,從而提升運行效率。
多個標誌可經過按位OR(|)進行鏈接,好比:re.I|re.M
修飾符 | 描述 |
---|---|
re.I | 使匹配對大小寫不敏感 |
re.L | 作本地化識別(locale-aware)匹配 |
re.M | 多行匹配,影響 ^ 和 $ |
re.S | 使 . 匹配包括換行在內的全部字符 |
re.U | 根據Unicode字符集解析字符。這個標誌影響 \w, \W, \b, \B. |
re.X | 該標誌經過給予你更靈活的格式以便你將正則表達式寫得更易於理解。 |
爲了告訴編譯器這個string是個raw string(原字符串),不要轉義反斜槓! 好比在raw string裏\n是兩個字符,''和'n',不是換行!
字符 | 做用 |
---|---|
. |
匹配任意一個字符(除了\n) |
[] |
匹配[]中列舉的字符 |
[^...] |
匹配不在[]中列舉的字符 |
\d |
匹配數字,0到9 |
\D |
匹配非數字 |
\s |
匹配空白,就是空格和tab |
\S |
匹配非空白 |
\w |
匹配字母數字或下劃線字符,a-z,A-Z,0-9,_ |
\W |
匹配非字母數字或下劃線字符 |
- |
匹配範圍,好比[a-f] |
字符 | 做用(前面三個作了優化,速度會更快,儘可能優先用前三個) |
---|---|
* |
前面的字符出現了0次或無限次,便可有可無 |
+ |
前面的字符出現了1次或無限次,即最少一次 |
? |
前面的字符出現了0次或者1次,要麼不出現,要麼只出現一次 |
{m} |
前一個字符出現m次 |
{m,} |
前一個字符至少出現m次 |
{m,n} |
前一個字符出現m到n次 |
字符 | 做用 |
---|---|
^ |
字符串開頭 |
$ |
字符串結尾 |
\b |
單詞邊界,即單詞和空格間的位置,好比'er\b' 能夠匹配"never" 中的 'er',但不能匹配 "verb" 中的 'er' |
\B |
非單詞邊界,和上面的\b相反 |
\A |
匹配字符串的開始位置 |
\Z |
匹配字符串的結束位置 |
用**()表示的就是要提取的分組**,通常用於提取子串, 好比:^(\d{3})-(\d{3,8})$:從匹配的字符串中提取出區號和本地號碼
字符 | 做用 |
---|---|
![]() |
匹配左右任意一個表達式 |
(re) |
匹配括號內的表達式,也表示一個組 |
(?:re) | 同上,可是不表示一個組 |
(?P<name>) |
分組起別名,group能夠根據別名取出,好比(?P<first>\d) match後的結果調m.group('first')能夠拿到第一個分組中匹配的記過 |
(?=re) |
前向確定斷言,若是當前包含的正則表達式在當前位置成功匹配, 則表明成功,不然失敗。一旦該部分正則表達式被匹配引擎嘗試過, 就不會繼續進行匹配了;剩下的模式在此斷言開始的地方繼續嘗試。 |
(?!re) |
前向否認斷言,做用與上面的相反 |
(?<=re) |
後向確定斷言,做用和(?=re)相同,只是方向相反 |
(?<!re) |
後向否認斷言,做用於(?!re)相同,只是方向想法 |
不引入括號,增個表達式做爲一個組,是group(0)
不引入**()的話,表明整個表達式做爲一個組,group = group(0) 若是引入()**的話,會把表達式分爲多個分組,好比下面的例子:
輸出結果:
除了group方法外還有三個經常使用的方法:
正則匹配默認是貪婪匹配,也就是匹配儘量多的字符。 好比:ret = re.match(r'^(\d+)(0*)$','12345000').groups()
ß 咱們的原意是想獲得**('12345','000')這樣的結果,可是輸出 ret咱們看到的倒是:
流程分析:
^(0|86|17951)?(13[0-9]|14[579]|15[0-35-9]|17[01678]|18[0-9])[0-9]{8}$
複製代碼
流程分析:
身份證號碼分爲一代和二代,一代由15位號碼組成,而二代則是由18個號碼組成: 十五位:xxxxxx yy mm dd pp s 十八位:xxxxxx yyyy mm dd ppp s
爲了方便了解,把這兩種狀況分開,先是十八位的:
能推算出18的,那麼推算出15的也不難了:
最後用|組合下:
^[1-9]\d{5}(18|19|20)\d{2}(0[1-9]|10|11|12)([012][1-9]|10|20|30|31)\d{3}[0-9Xx]|[1-9]\d{5}\d{2}(0[1-9]|10|11|12)([012][1-9]|10|20|30|31)\d{2}[0-9Xx]$
複製代碼
另外,這裏的正則匹配出的身份證不必定是合法的,判斷身份是否 合法還須要經過程序進行校驗,校驗最後的校驗碼是否正確。
擴展閱讀:身份證的最後一位是怎麼算出來的? 更多可見:第二代身份證號碼編排規則
首先有個加權因子的表:(沒弄懂怎麼算出來的..) [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2]
而後位和值想乘,結果相加,最後除11求餘,好比我隨便網上找的 一串身份證:411381199312150167,咱們來驗證下最後的7是對的嗎?
sum = 47 + 19 + 110 + 35 +88 + 1 4 ... + 6 * 2 = 282 sum % 11 = 7,因此這個是一個合法的身份證號。
流程分析:
ip由4段組成,xxx.xxx.xxx.xxx,訪問從0到255,由於要考慮上中間的. 因此咱們把第一段和後面三段分開,而後分析下ip的結構,多是這幾種狀況: 一位數:[1-9] 兩位數:[1-9][0-9] 三位數(100-199):1[0-9][0-9] 三位數(200-249):2[0-4][0-9] 三位數(250-255): 25[0-5] 理清了第一段的正則怎麼寫就一清二楚了:
.
**,而後這玩意是元字符, 須要加上一個反斜槓\,讓他失去做用,後面三段的正則就是:
^([1-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])(\.([1-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])){3}$
複製代碼
匹配中文:[\u4e00-\u9fa5]
匹配雙字節字符:[^\x00-\xff]
匹配數字並輸出示例:
匹配開頭結尾示例:
原本想着就抓抓中國氣象局的天氣就行了,而後呢,好比深圳天氣的網頁是: www.weather.com.cn/weather1dn/… 而後這個101280601是城市編碼,而後網上搜了下城市編碼列表,發現要麼 不少是錯的,要麼就缺失不少,或者連接失效,想一想本身想辦法寫一個採集 的,先搞一份城市編碼的列表,不過我去哪裏找數據來源呢?中國氣象局 確定是會有的,只是應該不會直接所有暴露出來,想一想能不能經過一些間接 操做來實現。對着中國氣象局的網站瞎點,結果不負有心人,我在這裏: www.weather.com.cn/forecast/ 發現了這個:
F12打開開發者工具,不出所料:
這裏有個超連接,難不成是北京全部的地區的列表,點擊下進去看看: www.weather.com.cn/textFC/beij…
臥槽,果真是北京全部的地區,而後每一個地區的名字貌似都有一個超連接, F12看下指向哪裏?
到這裏就豁(huo)然開朗了,咱們來捋一捋實現的流程:
流程看上去很簡單,接着來實操一波。
先是拿城市列表url
這個很容易拿,就直接貼代碼了:
拿到須要的城市列表url:
接着隨便點開一個,好比beijing.shtml,頁面結構是這樣的: 想要的內容是這裏的超連接:
F12看下頁面結構,層次有點多,不過不要緊,這樣更可以鍛鍊咱們
入手點通常都是離咱們想要數據最近地方下手,我看上了:conMidtab3 全局搜了一下,也就八個:
第一個直接就能夠排除了:
接着其他的七個,而後發現都他麼是同樣的...,那就直接抓到第一個吧:
輸出下:
是咱們想要的內容,接着裏面的tr是咱們須要內容,找一波:
輸出下:
繼續細扒,咱們要的只是a這個東西:
輸出下:
重複出現了一堆詳情,很明顯是咱們不想要的,咱們能夠在循環的時候 執行一波判斷,重複的不加入到列表中:
而後咱們想拿到城市編碼和城市名稱這兩個東西:
城市的話還好,直接調用tag對象的string直接就能拿到, 而城市編碼的話,按照之前的套路,咱們須要先['href']拿到 再作字符串裁剪,挺繁瑣的,既然本節學習了正則,爲什麼不用 正則來一步到位,不難寫出這樣的正則:
匹配拿到**group(1)**就是咱們要的城市編碼:
輸出內容:
臥槽,就是咱們想要的結果,美滋滋,接着把以前拿到全部 的城市列表都跑一波,存字典裏返回,最後賽到一個大字典 裏,而後寫入到文件中,完成。
========= BUG的分割線 =========
最後把數據打印出來發現只有428條數據,後面才發現conMidtab3那裏處理有些 問題,漏掉了一些,限於篇幅,就不從新解釋了,直接貼上修正完後的代碼把...
import urllib.request
from urllib import error
from bs4 import BeautifulSoup
import os.path
import re
import operator
# 經過中國氣象局抓取到全部的城市編碼
# 中國氣象網基地址
weather_base_url = "http://www.weather.com.cn"
# 華北天氣預報url
weather_hb_url = "http://www.weather.com.cn/textFC/hb.shtml#"
# 得到城市列表連接
def get_city_list_url():
city_list_url = []
weather_hb_resp = urllib.request.urlopen(weather_hb_url)
weather_hb_html = weather_hb_resp.read().decode('utf-8')
weather_hb_soup = BeautifulSoup(weather_hb_html, 'html.parser')
weather_box = weather_hb_soup.find(attrs={'class': 'lqcontentBoxheader'})
weather_a_list = weather_box.findAll('a')
for i in weather_a_list:
city_list_url.append(weather_base_url + i['href'])
return city_list_url
# 根據傳入的城市列表url獲取對應城市編碼
def get_city_code(city_list_url):
city_code_dict = {} # 建立一個空字典
city_pattern = re.compile(r'^<a.*?weather/(.*?).s.*</a>$') # 獲取城市編碼的正則
weather_hb_resp = urllib.request.urlopen(city_list_url)
weather_hb_html = weather_hb_resp.read().decode('utf-8')
weather_hb_soup = BeautifulSoup(weather_hb_html, 'html.parser')
# 須要過濾一波無效的
div_conMidtab = weather_hb_soup.find_all(attrs={'class': 'conMidtab', 'style': ''})
for mid in div_conMidtab:
tab3 = mid.find_all(attrs={'class': 'conMidtab3'})
for tab in tab3:
trs = tab.findAll('tr')
for tr in trs:
a_list = tr.findAll('a')
for a in a_list:
if a.get_text() != "詳情":
# 正則拿到城市編碼
city_code = city_pattern.match(str(a)).group(1)
city_name = a.string
city_code_dict[city_code] = city_name
return city_code_dict
# 寫入文件中
def write_to_file(city_code_list):
try:
with open('city_code.txt', "w+") as f:
for city in city_code_list:
f.write(city[0] + ":" + city[1] + "\n")
except OSError as reason:
print(str(reason))
else:
print("文件寫入完畢!")
if __name__ == '__main__':
city_result = {} # 建立一個空字典,用來存全部的字典
city_list = get_city_list_url()
# get_city_code("http://www.weather.com.cn/textFC/guangdong.shtml")
for i in city_list:
print("開始查詢:" + i)
city_result.update(get_city_code(i))
# 根據編碼從升序排列一波
sort_list = sorted(city_result.items(), key=operator.itemgetter(0))
# 保存到文件中
write_to_file(sort_list)
複製代碼
運行結果:
本節對Python中了正則表達式進行了一波學習,練手,發現和Java裏的正則 多了一些規則,正則在字符串匹配的時候是挺爽的,可是正則並非全能 的,好比閏年二月份有多少天的那個問題,還須要程序另外去作判斷! 正則還須要多練手啊,限於篇幅,就沒有另外去抓各類天氣信息了, 並且不是剛需,順道提供兩個免費可用三個和能拿到天氣數據的API吧:
還有個中國氣象局提供的根據經緯度獲取天氣的: e.weather.com.cn/d/town/inde…
人生苦短,我用Python,爬蟲真好玩!期待下節爬蟲框架scrapy學習~
來啊,Py交易啊
想加羣一塊兒學習Py的能夠加下,智障機器人小Pig,驗證信息裏包含: Python,python,py,Py,加羣,交易,屁眼 中的一個關鍵詞便可經過;
驗證經過後回覆 加羣 便可得到加羣連接(不要把機器人玩壞了!!!)~~~ 歡迎各類像我同樣的Py初學者,Py大神加入,一塊兒愉快地交流學♂習,van♂轉py。