[网络爬虫基础] 3. 正则表达式
文章目录
一、正则表达式的语法
1. 正则表达式常用操作符
操作符 | 说明 | 实例 |
---|---|---|
. | 表示任何单个字符 | |
[ ] | 字符集,对单个字符给出取值范围 | [abc] 表示a、b、c,[a‐z] 表示a到z单个字符 |
[^ ] | 非字符集,对单个字符给出排除范围 | [^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_] |
2. 经典正则表达式
正则表达式 | 说明 |
---|---|
^[A‐Za‐z]+$ | 由26个字母组成的字符串 |
^[A‐Za‐z0‐9]+$ | 由26个字母和数字组成的字符串 |
^‐?\d+$ | 整数形式的字符串 |
^[0‐9]*[1‐9][0‐9]*$ | 正整数形式的字符串 |
[1‐9]\d{5} | 中国境内邮政编码,6位 |
[\u4e00‐\u9fa5] | 匹配中文字符 |
\d{3}‐\d{8}|\d{4}‐\d{7} | 国内电话号码,010‐68913536 |
3. 匹配IP地址的正则表达式
IP地址字符串形式的正则表达式(IP地址分4段,每段0‐255)
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])
二、Re库的基本使用
1. 正则表达式的表示类型
raw string类型(原生字符串类型)是不包含对转义符再次转义的字符串
re库采用raw string类型表示正则表达式,表示为:r’text’,例如: r'\d{3}‐\d{8}|\d{4}‐\d{7}’
re库也可以采用string类型表示正则表达式,但更繁琐,例如:'\\d{3}‐\\d{8}|\\d{4}‐\\d{7}'
建议当正则表达式包含转义符时,使用raw string
2. re库主要功能函数
函数 | 说明 |
---|---|
re.search() | 在一个字符串中搜索匹配正则表达式的第一个位置,返回match对象 |
re.match() | 从一个字符串的开始位置起匹配正则表达式,返回match对象 |
re.findall() | 搜索字符串,以列表类型返回全部能匹配的子串 |
re.split() | 将一个字符串按照正则表达式匹配结果进行分割,返回列表类型 |
re.finditer() | 搜索字符串,返回一个匹配结果的迭代类型,每个迭代元素是match对象 |
re.sub() | 在一个字符串中替换所有匹配正则表达式的子串,返回替换后的字符串 |
re.search(pattern, string, flags=0)
返回match对象
- pattern : 正则表达式的字符串或原生字符串表示
- string : 待匹配字符串
- flags : 正则表达式使用时的控制标记
常用标记flags | 说明 |
---|---|
re.I re.IGNORECASE | 忽略正则表达式的大小写,[A‐Z]能够匹配小写字符 |
re.M re.MULTILINE | 正则表达式中的^ 操作符能够将给定字符串的每行当作匹配开始 |
re.S re.DOTALL | 正则表达式中的. 操作符能够匹配所有字符,默认匹配除换行外的所有字符 |
re.match(pattern, string, flags=0)
返回match对象,和re.search最大的不同在于match从开始位置起匹配
>>> import re
>>> match=re.search(r'[1-9]\d{5}','BIT 100081')
>>> if match:
print(match.group(0))
100081
>>> match=re.match(r'[1-9]\d{5}','BIT 100081')
>>> match.group(0)
AttributeError: 'NoneType' object has no attribute 'group'
>>> match=re.match(r'[1-9]\d{5}','100081 BIT')
>>> if match:
print(match.group(0))
100081
re.findall(pattern, string, flags=0)
以列表形式返回匹配的全部子串
re.split(pattern, string, maxsplit=0, flags=0)
将字符串按照正则表达式匹配结果进行分割
- maxsplit: 最大分割数,剩余部分作为最后一个元素输出
re.finditer(pattern, string, flags=0)
返回一个匹配结果的迭代类型,每个迭代元素是match对象
re.sub(pattern, repl, string, count=0, flags=0)
替换所有匹配正则表达式的子串,返回替换后的字符串 - repl : 替换匹配字符串的字符串
- count : 匹配的最大替换次数
>>> import re
>>> re.findall(r'[1-9]\d{5}','BIT100081 TSU100084')
['100081', '100084']
>>> re.split(r'[1-9]\d{5}','BIT100081 TSU100084')
['BIT', ' TSU', '']
>>> re.split(r'[1-9]\d{5}','BIT100081 TSU100084',maxsplit=1)
['BIT', ' TSU100084']
>>> for m in re.finditer(r'[1-9]\d{5}','BIT100081 TSU100084'):
if m:
print(m.group(0))
100081
100084
>>> re.sub(r'[1-9]\d{5}',':zipcode','BIT100081 TSU100084')
'BIT:zipcode TSU:zipcode'
3. re库的另一种方法
-
函数式用法:一次性操作
rst = re.search(r’[1‐9]\d{5}’, ‘BIT 100081’)
-
面向对象用法:编译后的多次操作
pat = re.compile(r’[1‐9]\d{5}’) rst = pat.search(‘BIT 100081’)
regex = re.compile(pattern, flags=0)
将正则表达式的字符串形式编译成正则表达式对象:
三、Re库的Match对象
Match对象是一次匹配的结果,包含匹配的很多信息
1. Match对象的属性
属性 | 说明 |
---|---|
.string | 待匹配的文本 |
.re | 匹配时使用的patter对象(正则表达式) |
.pos | 正则表达式搜索文本的开始位置 |
.endpos | 正则表达式搜索文本的结束位置 |
2. Match对象的方法
方法 | 说明 |
---|---|
.group(0) | 获得匹配后的字符串 |
.start() | 匹配字符串在原始字符串的开始位置 |
.end() | 匹配字符串在原始字符串的结束位置 |
.span() | 返回(.start(), .end()) |
>>> import re
>>> m=re.search(r'[1-9]\d{5}','BIT 100081')
>>> type(m)
<class 're.Match'>
>>> m.string
'BIT 100081'
>>> m.re
re.compile(r'[1-9]\d{5}')
>>> m.pos
0
>>> m.endpos
10
>>> m.group(0)
'10081'
>>> m.start()
4
>>> m.end()
10
>>> m.span()
(4,10)
四、Re库的贪婪匹配和最小匹配
Re库默认采用贪婪匹配,即输出匹配最长的子串。如果我们需要输出最短的子串,就要用到最小匹配操作符。
最小匹配操作符 | 说明 |
---|---|
*? | 前一个字符0次或无限次扩展,最小匹配 |
+? | 前一个字符1次或无限次扩展,最小匹配 |
?? | 前一个字符0次或1次扩展,最小匹配 |
{m,n}? | 扩展前一个字符m至n次(含n),最小匹配 |
只要长度输出可能不同的,都可以通过在操作符后增加?
变成最小匹配
>>> import re
>>> match = re.search(r'PY.*N', 'PYANBNCNDN')
>>> match.group(0)
'PYANBNCNDN'
>>> match = re.search(r'PY.*?N', 'PYANBNCNDN')
>>> match.group(0)
'PYAN'
五、淘宝商品比价定向爬虫
- 目标:获取淘宝搜索页面的信息,提取其中的商品名称和价格
- 理解:淘宝的搜索接口
- 技术路线:request-bs4-re
程序的结构设计
-
步骤1:提交商品搜索请求,循环获取页面
-
步骤2:对于每个页面,提取商品名称和价格信息
-
步骤3:将信息输出到屏幕上
1. 写框架
# -*- coding: utf-8 -*-
import requests
import re
# 获取页面
def getHTMLText(url):
print("")
# 对获得的每个页面进行解析
def parsePage(ilt, html):
print("")
#将商品信息输出
def printGoodsList(ilt):
print("")
def main():
goods = '书包'
depth = 2
start_url = 'http://s.taobao.com/search?q=' + goods
infoList = [] # 输出结果
for i in range(depth):
try:
url = start_url + '&s=' + str(44*i) # 44*i对于第一个页面,以44为倍数
html = getHTMLText(url)
parsePage(infoList, html)
except:
continue # 异常,就下一页继续
printGoodsList(infoList)
if __name__ == '__main__':
main()
2. 完善函数
注意:在淘宝获取页面时,淘宝设置了登录验证才能访问,所以在requests请求时,添加cookies
和user-agent
。
2.1 getHTMLText()
def getHTMLText(url):
try:
# 添加cookies和user-agent
coo = '...'
cookies = {}
for line in coo.split(';'):
name, value = line.strip().split('=', 1)
cookies[name] = value
headers = {...}
r = requests.get(url, cookies=cookies, headers=headers, timeout=30)
r.raise_for_status
r.encoding = r.apparent_encoding
print(r.request.url)
return r.text
except:
print("获取失败")
return ""
2.2 parsePage()
解析页面使用两个变量,ilt 是对结果的列表类型,html 是页面信息。
从页面源代码中发现
① “view_price”:“119.00”,于是使用正则表达式匹配。r'\"view_price\"\:\"[\d\.]*\"'
② “raw_title”:“kk树书包小学生男孩1-3-4-5年级儿童背包”,于是用r'\"raw_title\"\:\".*?\"'
(这里是最小匹配)
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]) # eval去掉字符串外面的双引号或单引号
title = eval(tlt[i].split(':')[1])
ilt.append([price, title])
except:
print("")
2.3 printGoodsList()
#输入到屏幕
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]))
3. run
import requests
import re
def getHTMLText(url,kv,cookies):
try:
r = requests.get(url, headers=kv,cookies=cookies,timeout=30)
r.raise_for_status()
r.encoding = r.apparent_encoding
return r.text
except:
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)):
#eval函数执行一个字符串表达式
price = eval(plt[i].split(':')[1])
title = eval(tlt[i].split(':')[1])
ilt.append([price, title])
except:
print("")
#输入到屏幕
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
coo='t=41653b20706345d12631aafb017b970f; cna=wKFrFnvBNRsCAXyATYJvStxu; thw=cn; v=0; cookie2=161c76018f25b9311c464719c2aa38ba; _tb_token_=e683b3559e733; unb=2212782603; uc3=lg2=UIHiLt3xD8xYTw%3D%3D&vt3=F8dByuqh437%2Fb%2FHhiE4%3D&id2=UUpgRKr%2BqoQHqA%3D%3D&nk2=pbJvuSGAeVW8Zg%3D%3D; csg=fa67ab25; lgc=%5Cu8D77%5Cu98CE%5Cu4E86%5Cu7A57%5Cu5B50; cookie17=UUpgRKr%2BqoQHqA%3D%3D; dnk=%5Cu8D77%5Cu98CE%5Cu4E86%5Cu7A57%5Cu5B50; skt=95221cd1c2aa6cc0; existShop=MTU3NzE2OTIzNA%3D%3D; uc4=nk4=0%40pwW3VMB6m4v7c%2B1Av8qXNXHpKjuL&id4=0%40U2gqy1t91FKKDK9ChmZT%2BdsbZc8w; tracknick=%5Cu8D77%5Cu98CE%5Cu4E86%5Cu7A57%5Cu5B50; _cc_=U%2BGCWk%2F7og%3D%3D; tg=0; _l_g_=Ug%3D%3D; sg=%E5%AD%9032; _nk_=%5Cu8D77%5Cu98CE%5Cu4E86%5Cu7A57%5Cu5B50; cookie1=WvcYr4FuAHzJNq3CI7IDe0Vs2KH66XulIazTWuEHUKc%3D; enc=ZD%2B5tM4n0urXXyxYIdz7Z4LiPZNpca1RbpyqDcJbXdWrbo8pPCGUegPxhgUbAxO8z9dvygzlF3kQtcGOKB%2B%2FiA%3D%3D; mt=ci=91_1; hng=CN%7Czh-CN%7CCNY%7C156; uc1=cookie14=UoTbmhFo9Fy9aA%3D%3D&cookie15=W5iHLLyFOGW7aA%3D%3D; JSESSIONID=9AF926C20DC6A16E96A28DA11B9E3E84; l=cBP5-TLVq8T4HoOsBOCwourza77OSIRAguPzaNbMi_5CL6L66QbOoj_xYFp6VjWdTp8B4Ydbw2w9-etui-y06Pt-g3fP.; isg=BBYWvAMGVOBtyGOtf5Y7ZEB6Z8wYt1rxxS69T4B_AvmUQ7bd6EeqAXyx258Ka1IJreferer: https://s.taobao.com/search?q=%E4%B9%A6%E5%8C%85&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'
cookies={}
for line in coo.split(';'):
name,value=line.strip().split('=',1)
cookies[name]=value
kv={'user-agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36'}
#对输出结果定义一个变量
infoList = []
for i in range(depth):
try:
url = start_url + '&s=' + str(44 * i)
html = getHTMLText(url,kv,cookies)
parsePage(infoList, html)
except:
continue
printGoodsList(infoList)
main()
输出结果
序号 价格 商品名称
1 189.00 瑞士军刀初中生书包男双肩包小学生背包高中
2 98.00 迪士尼书包小学生女童1-3-6年级冰雪公主女孩轻便儿童休闲双肩包8
3 119.00 kk树书包小学生男孩1-3-4-5年级儿童背包女孩6-12周岁双肩包护脊
4 134.00 鳄鱼男士双肩包商务休闲电脑帆布背包旅游旅行包时尚潮流学生书包
5 59.00 迪士尼书包小学生男女1-3-4-6年级米奇减负背包儿童书包8-10-12岁
6 89.00 迪士尼书包男小学生1-3-6三到六4年级儿童双肩护脊减负轻便背包女
7 119.00 kk树书包小学生女孩6-12岁儿童一二三到六年级女童双肩包护脊减负
8 59.00 电脑背包男士双肩包旅行大容量时尚潮流高中初中学生书包女大学生
9 59.00 巴布豆旗舰店书包1-3年级护脊减负儿童书包男4-6小学生书包轻便
...
六、股票数据定向爬虫
目标: 获取上交所和深交所所有股票的名称和交易信息。
网站选择原则: 股票信息静态存在于html页面中,非js代码生成,没有Robbts协议限制。
- 步骤1: 从凤凰网财经获取股票列表;
- 步骤2: 逐一获取股票代码,并增加到老虎社区股票的链接中,最后对这些链接进行逐个的访问获得股票的信息;
- 步骤3: 将结果存储到文件。
getStockList()
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
处理每个a标签的详细步骤:
- 找到a标签中的href属性,并且判断属性中间的链接,把链接后面的数字取出来,在这里可以使用正则表达式来进行匹配。由于深圳交易所的代码以sz开头,上海交易所的代码以sh开头,股票的数字有6位构成,所以正则表达式可以写为
[s][hz]\d{6}
。 - 由于在html中有很多的a标签,但是有些a标签中没有href属性,因此上述程序在运行的时候出现异常,所有对上述的程序还要进行
try...except
来对程序进行异常处理,使用了continue语句,直接让其跳过。
getStockInfo()
def getStockInfo(lst, stockURL, fpath):
## 去掉列表里的重复选项--将列表转换为集合再转换为列表
lst = list(set(lst))
## 遍历每个股票
count = 0
for stock in lst:
url = stockURL + stock[-6:]
# 获取单个股票的详情页
html = getHTMLText(url)
try:
# 对html代码进行解析,单个股票的信息存放在标签为div,属性为stock-info的html代码中
if html == '': ## 判断是否空页面
continue
infoDict = {} ## 定义一个字典,存储股票信息
soup = BeautifulSoup(html, 'html.parser')
stockInfo = soup.find('div', attrs={'class':'stock-info'})
#股票名称在name标签内,价格在latest标签内
name = stockInfo.find_all(attrs={'class':'name'})[0]
price = stockInfo.find_all(attrs={'class': 'latest'})[0]
infoDict.update({'股票名称':name.text.split()[0], '最新行情':price.text.split()[0]})
#股票的其他信息存放在dt和dd标签中,其中dt表示股票信息的键域,dd标签是值域。获取全部的键和值:
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)), end='')
except:
traceback.print_exc() ## 获得发生异常的错误信息
continue
完整代码
import requests
from bs4 import BeautifulSoup
import re
import traceback
def getHTMLText(url, code='utf-8'):
try:
r = requests.get(url)
r.raise_for_status()
r.encoding = code
return r.text
except:
print('爬取失败')
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):
lst = list(set(lst))
count = 0
for stock in lst:
url = stockURL + stock[-6:]
html = getHTMLText(url)
try:
if html == '':
continue
infoDict = {}
soup = BeautifulSoup(html, 'html.parser')
stockInfo = soup.find('div', attrs={'class':'stock-info'})
name = stockInfo.find_all(attrs={'class':'name'})[0]
price = stockInfo.find_all(attrs={'class': 'latest'})[0]
infoDict.update({'股票名称':name.text.split()[0], '最新行情':price.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)), end='')
except:
traceback.print_exc()
continue
def main():
stock_list_url = 'http://app.finance.ifeng.com/list/stock.php?t=ha'
stock_info_url = 'https://www.laohu8.com/stock/'
output_file = 'D:/BaiduStockInfo.txt'
slist=[]
getStockList(slist, stock_list_url)
getStockInfo(slist, stock_info_url, output_file)
main()