正则表达式(Regular Expression):用来简洁表达一组字符串的方式。
正则表达式的优点:简洁!正则表达式表达的是一组字符串的特征。
‘P‘
‘PY‘
‘PYYY‘
...
#正则表达式为:PY+,表示P后面跟着若干个Y
#若一个字符串以‘PY‘开头而总共不超过10个字符串且后续不能再出现‘P‘或者‘Y‘,那么:
#正则表达式为:PY[^PY]{0,10}
我们可以认为正则表达式是通用的表达框架,它是一种针对字符串表达的”简洁“和“特征”的功能。正则表达式用于判断某个字符串是否属于一组字符串。
正则表达式的用途:
regx = ‘PY[^PY]{0,10}‘ #编译前的正则表达式只是一个字符串
p = re.compile(regx) #编译后才成为了一个真正的正则表达式
正则表达式是由字符和操作符构成的。
常用的操作符:
操作符 | 说明 | 实例 |
---|---|---|
. | 表示任意单个字符 | |
[] | 字符集,对单个字符给出取值范围 | a[b,c]表示ab或ac,[a-z]表示从a到z的单个字符 |
[^] | 非字符集,对单个字符给出排除范围 | [^abc]表示除了a,b和c以外的单个字符 |
* | 表示前一个字符出现0次或若干次 | abc*表示ab, abc, abcc, abccc等等 |
+ | 表示前一个字符出现1次或若干次 | abc+表示abc, abcc, abccc等等 |
? | 表示前一个字符出现0次或1次 | a[b]?表示a, ab等等 |
| | 左右表达式任意一个 | abc|def 表示abc和def任选一个 |
{m} | 表示扩展前一个字符m次 | ab{2}c表示abbc |
{m, n} | 表示扩展前一个字符m到n次 | ab{1, 2}c表示abc,abbc |
^ | 没加“[]”表示匹配字符串的开头 | ^abc表示abc在一个字符串的开头 |
$ | 表示匹配字符串的末尾 | abc$表示abc在一个字符串的末尾 |
() | 分组标记,内部只能使用 | 符号 | (abc|def)表示abc或def |
\d | 表示数字,等价于[0-9] | |
\w | 表示字母或数字或下划线等价于[a-zA-Z0-9_] |
以下给出若干实例:
>>>P(Y|YT|YTH|YTHO)?N
"PN", "PYN", "PYTN", "PYTHN", "PYTHON"
>>>PN+
"PN", "PNN", "PNNN", "PNNNN"...
>>>PY[TH]ON
"PYTON", "PYHON"
>>>PY[^TH]?ON
"PYON", "PYAON", "PYBON"...
>>>PY{:3}N
"PN", "PYN", "PYYN", "PYYYN"
#一些经典的正则表达式:
>>>^[A-Za-z]+$ #表示26个字母组成的字符串
>>>^[A-za-z0-9]+$ #表示以英文字符或者数字组成的字符串的正则表达式
>>>^-?\d$ #表示整数形式(含负数)的正则表达式
>>>^[0-9]*[1-9][0-9]*$ #表示正整数形式的正则表达式
>>>[1-9]\d{5} #表示国内邮政编码的正则表达式
>>>[\u4e00-\u9fa5] #表示中文字符的正则表达式
>>>\d{3}-\d{8}|\d{4}-\d{7} #表示的是国内固定电话的正则表达式
>>>([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].) #表示的是IP地址的正则表达式
主要用于字符串匹配。
raw string类型(原生字符串类型):
原生字符串类型是指不包括转义符的字符串。只需要在字符串前面加一个r即可,例如:r"text"。
同样也可以使用字符串类型来表达,但是必须在“\”时需要增加额外的“\”.
因此,当正则表达式中含有转义符的时候,应该使用raw string来表达。
函数 | 功能 |
---|---|
re.search() | 在一个字符串中搜索匹配正则表达式的第一个位置,并返回一个match对象 |
re.match() | 从一个字符串的开始位置起匹配正则表达式,返回一个match对象 |
re.findall() | 搜索字符串,以列表类型返回全部能匹配的子串 |
re.split() | 将一个字符串按照正则表达式的结果进行分割,返回列表类型 |
re.findall() | 搜索字符串返回一个匹配结果的迭代类型,每个元素是一个match对象 |
re.sub | 在一个字符串中,替换所有匹配的正则表达式子串,返回替换后的字符串 |
在一个字符串中匹配第一个正则表达式的位置,返回一个match对象。
pattern:正则表达式字符串或原生字符串表示。
string:待匹配字符串
flags:正则表达式的控制标记。主要有以下三个。
常用标记 | 说明 |
---|---|
re.I (re.IGNORANCECASE) | 忽略正则表达式的大小写区分,如[A-Z]也能表示小写的[a-z] |
re.M (re.MULTILINE) | 作用于^,能使得正则表达式对字符串每一行都进行匹配 |
re.S (re.DOTALL) | 正则表达式中的 . 默认匹配除了换行符以外的所有字符 使用这一标记符后它也可以匹配换行符了 |
import re
match = re.research(r‘[1-9]\d{5}‘, ‘BIT 100081‘)
if match:
print(match.group(0))
从一个字符串的开始位置起匹配正则表达式,返回一个match对象。
它的参数同re.research是一样的。
re.match(r‘[1-9]\d{5}‘, ‘BIT 100081‘)
match.group(0)
>>>Error #这里没有匹配的结果,匹配时必须加入if match:
re.match(r‘[1-9]\d{5}‘, ‘100081 BIT‘)
if match:
match.group(0)
>>>‘100081‘
搜索字符串,返回一个全部匹配结果的列表类型。
参数同前两个。
ls = re.findall(r‘[1-9]\d{5}‘, ‘BIT100081 TSU100084‘)
print(ls)
>>>[‘100081‘, ‘100084‘]
表示把目标字符串按照指定的模式分割出来。
参数中maxsplit表示最大分割数,若超过,则把剩下的所有的内容作为列表的最后一个参数返回。
re.split(r‘[1-9]\d{5}‘, ‘BIT10081 TSU10084‘)
>>>[‘BIT‘, ‘ TSU‘, ‘‘]
re.split(r‘[1-9]\d{5}‘, ‘BIT10081 TSU10084‘, naxsplit=1)
>>>[‘BIT‘, ‘ TSU10084‘]
搜索目标字符串返回一个迭代类型,迭代类型的每一个元素是match对象。
for m in re.finditer(r‘[1-9]\d{5}‘, ‘BIT100081 TSU100084‘):
if m:
print(m.group(0))
>>>10081
10084
搜索目标字符串,将匹配的部分替换为另一个字符串,返回匹配后的字符串。
参数中,count表示最大替换次数,rpel表示替换的字符串。
>>> import re
>>> re.sub(r‘[1-9]\d{5}‘, "zipcode:******","BIT100081 TSU100084")
‘BITzipcode:****** TSUzipcode:******‘
>>> re.sub(r‘[1-9]\d{5}‘, "zipcode:******","BIT100081 TSU100084",count=1)
‘BITzipcode:****** TSU100084‘
re.search(r‘[1-9]\d{5}‘, ‘BIT100081 TSU100084‘)
pat = re.compile(r‘[1-9]\d{5}‘)
pat.research("BIT100084")
这样多次调用只需一次编译,可以加快运行速度。
同样它有六种方法,同re库的使用是一样的,只是无需正则表达式中pattern的参数。
属性 | 说明 |
---|---|
match.string | 匹配的目标字符串 |
match.re | 匹配的模板 |
match.pos | 匹配在目标字符串的起始位置 |
match.endpos | 匹配在目标字符串的结束位置 |
方法 | 说明 |
---|---|
match.group(0) | 匹配的结果,有多个时只返回第一个 |
match.start() | 匹配结果在目标字符串的开始位置 |
match.end() | 匹配结果在目标字符串的结束位置 |
match.span() | 返回一个元组类型,(.start(), .end()) |
>>>import re
>>> m = re.search(r‘[1-9]\d{5}‘, "BIT100081 TSU100084")
>>> m.string
‘BIT100081 TSU100084‘
>>> m.re
re.compile(‘[1-9]\\d{5}‘)
>>> m.pos
0
>>> m.endpos
19
>>> m.group(0) #只显示第一个搜寻结果,如果要显示多个,应该使用finditer()遍历
‘100081‘
>>> m.start()
3
>>> m.end()
9
>>> m.span()
(3, 9)
re库默认采用贪婪匹配:即,在一个给定字符串中,返回最长的哪个匹配的子串。
如果希望返回的是最小匹配,那么要对原本的操作符进行一定的扩展。
操作符 | 说明 |
---|---|
*? | 前一个字符0次或无限次循环,取最小匹配 |
+? | 前一个字符1次或无限次循环,取最小匹配 |
?? | 前一个字符出现0或1次,取最小匹配(0次) |
{m,n}? | 前一个字符出现m到n(含n)次,取最小匹配 |
>>>m = re.search(r‘PY.*N‘, "PYANBNCNDN")
>>>m.group(0)
"PYANBNCNDN"
>>>m = re.search(r‘PY.*?N‘, "PYANBNCNDN")
>>>m.group(0)
"PYAN"
import requests
import re
cookie = #自己登陆后header里的cookie,涉及隐私信息不宜公开
cookie = cookie.encode("utf-8")
#有时会报编码错误,因为淘宝的cookie里有各种稀奇古怪的字符,包括中文,默认的latin-1有时无法解码
hd = {
"user-agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.128 Safari/537.36",
"cookie":cookie
}
def getHTMLText(url):
try:
r = requests.get(url, headers=hd)
r.raise_for_status()
r.encodiding = r.apparent_encoding
return r.text
except:
print("爬取错误!")
return ""
def parsePage(itl, html):
try:
plt = re.findall(r‘\"view_price\"\:\"[\d\.]*\"‘, html)
tlt = re.findall(r‘\"raw_title\":\".*?\"‘, html)
llt = re.findall(r‘\"item_loc\":\".*?\"‘, html)
slt = re.findall(r‘\"view_sales\":\".*?\"‘, html)
nlt = re.findall(r‘\"nick\":\".*?\"‘, html)
for i in range(len(plt)):
p = eval(plt[i].split(":")[1])
t = eval(tlt[i].split(":")[1])
l = eval(llt[i].split(":")[1])
s = eval(slt[i].split(":")[1])
n = eval(nlt[i].split(":")[1])
itl.append([t, p, l, s, n])
except:
print("")
def printGoodsList(itl):
module = "{0:{5}^30}\t\t{1:^10}\t\t{2:^10}\t\t{3:^10}\t\t{4:^10}"
print(module.format("商品名称", "商品价格", "店铺所在地", "商品销量", "店铺名称", chr(12288)))
for i in itl:
print(module.format(i[0], i[1], i[2], i[3], i[4], chr(12288)))
def main():
goods = "书包"
depth = 2
start_url = "https://s.taobao.com/search?q=" + goods
infolist = []
for i in range(depth):
try:
url = start_url + "&s=" + str(44*i)
html= requests.get(url, headers=hd)
print(html.text)
parsePage(infolist, html.text)
except:
continue
printGoodsList(infolist)
main()
这个例子是个实用性很强的例子,我想在日常网购中还是能发挥很大作用的。
要点还是在于对正则表达式的理解和掌握,这里很显然就必须用到一个最小匹配,一旦默认使用 了贪婪匹配整个爬取就会完全错误了!
如果有一定的web开发基础,我想会更容易使用和理解这样的爬虫。另外,如果能把结果输出为一个CSV文件,用Excel进一步处理那么效果应该会很不错。
输入:获取所有上交所、深交所股票交易信息
输出:保存到文件中
技术路线:requests——bs4——re
选取网页的原则:应该选取那些将股票数据写在HTML页面中,不能由js代码生成的。同时,最好没有robots协议限制
选取方法:浏览器F12查看
选取心态:不必纠结于一个单一的网站。
import requests
import re
from bs4 import BeautifulSoup
import traceback
def getHTMLText(url):
try:
r = requests.get(url)
r.raise_for_status()
r.encoding = r.apparent_encoding
return r.text
except:
print("爬取错误")
return ""
def getFundList(lst, furl):
html = getHTMLText(furl)
soup = BeautifulSoup(html, "html.parser")
a = soup.find_all("tr")
for i in a:
try:
id = i.attrs["id"]
lst.append(re.findall(r"[tr]\d{6}", id)[0])
except:
continue
def getFundInfo(lst, fiurl, fpath):
for fund in lst:
url = fiurl + fund[1:] + ".html"
html = getHTMLText(url)
try:
if html == "":
continue
infoDict = {}
soup = BeautifulSoup(html, ‘html.parser‘)
fundInfo = soup.find(‘div‘, attrs={‘class‘: "merchandiseDetail"})
name = fundInfo.find_all(attrs={‘class‘: "fundDetail-tit"})[0]
infoDict.update({‘基金名称‘: name.text.split()[0]}) #这里对所有文本分割取第一个就是基金名字
keyList = fundInfo.find_all("dt")
valList = fundInfo.find_all("dd")
for i in range(len(keyList)):
key = keyList[i].text
val = valList[i].text
infoDict[key] = val
with open(fpath, ‘a‘,encoding="utf-8")as f:
f.write(str(infoDict)+‘\n‘)
except:
traceback.print_exc() #获取错误信息
continue
def main():
fund_url = "https://fund.eastmoney.com/fund.html#os_0;isall_0;ft_;pt_1"
fundinfo_url = "https://fund.eastmoney.com/"
fpath = "D://fundinfo.txt"
flist = []
getFundList(flist, fund_url)
getFundInfo(flist, fundinfo_url, fpath)
main()
网址都做了变更,现在要找一个静态网页确实不太容易了。
因此我感觉目前学的部分的爬虫还是由很大局限性的,数据量不上去就没办法更好地进行数据的分析吧。仅仅是简单的爬取的话,那么与直接在相关网页中搜索没有多大的区别。
以上内容来自北京理工大学嵩天老师及其团队的《Python网络爬虫与信息提取》课程第三周的内容。
非常感谢嵩天老师及其团队给我们带来这样优质的课程。
原文:https://www.cnblogs.com/modered-study/p/14709309.html