引言: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。