正则表达式
定向爬虫
在学习嵩天老师的python爬虫实例中的定向爬取中国好大学排行。时过境迁, 嵩天老师的代码也不太正确了。
踩了一些小小的坑,由此学习了正则表达式 。
?当然在此之前,先贴出定向爬取中国好大学排行的定向爬虫?吧
import requests
from bs4 import BeautifulSoup
import bs4
import re
def main():
uinfo = []
url = "https://www.shanghairanking.cn/rankings/bcur/2020"
htmlText = getHtmlText(url)
fillList(uinfo, htmlText)
printList(uinfo, 20)
def getHtmlText(url):
try:
r = requests.get(url)
r.raise_for_status()
r.encoding = r.apparent_encoding
return r.text
except:
print("获取失败")
return ""
def fillList(uinfo, htmlText):
soup = BeautifulSoup(htmlText, 'html.parser')
pattern = re.compile(r'[\n ]+')#正则表达式进行数据清理
for tr in soup.find('tbody').children:
if isinstance(tr, bs4.element.Tag):
tds = tr('td')
uinfo.append([pattern.sub('', tds[0].text),\
pattern.sub('', tds[1].text),\
pattern.sub('', tds[4].text)])
def printList(uinfo, num):
ptlhead = '{0:^10}\t{1:{3}^10}\t{2:^16}\t'
ptl = '{0:^10}\t{1:{3}^10}\t{2:^10}\t'
print(ptlhead.format('排名', '学校', '总分', ' '))
for i in range(num):
u = uinfo[i]
print(ptl.format(u[0], u[1], u[2], ' '))
main()
之所以用到了正则表达式是因为在爬取获得的数据后发现存在不少的换行符和空格 ,而使用了python原生的re库进行正则匹配。例如这一行数据
'\n 北京\n '
发现很多条数据都是这样带有一个换行符+n个空格干扰了我们想要提取的信息。用下面的正则表达式来匹配,该正则表达式的含义是匹配换行符并接着匹配空格1至n次
pattern = re.compile(r'[\n ]+')
好了,现在进入本篇博客的正文吧???
正则表达式
概念
正则表达式是对字符串操作的一种逻辑公式,就是用事先定义好的一些特定字符、及这些特定字符的组合,组成一个“规则字符串”,这个“规则字符串”用来表达对字符串的一种过滤逻辑。
简介
正则表达式是对字符串(包括普通字符(例如,a 到 z 之间的字母)和特殊字符(称为“元字符”))操作的一种逻辑公式,就是用事先定义好的一些特定字符、及这些特定字符的组合,组成一个“规则字符串”,这个“规则字符串”用来表达对字符串的一种过滤逻辑。正则表达式是一种文本模式,该模式描述在搜索文本时要匹配的一个或多个字符串。
概念的东西我们不多说???
正则表达式的语法
在这之前我想再次浓重的推荐一个神奇的在线正则网站貌似外国网站所以进去会慢一点,但是非常非常地好用??? 下面的实例可以在这个网站上实际操作一波。
当然因为这篇博客是放在python栏中的,而且本篇博客写的是python的re库中的正则表达式 。而re库默认采用的是贪婪匹配 。也就是说在有多个匹配结果时,总会尽可能选取长度教长的结果返回。而关于re库的非贪婪匹配会在后面根据实例讲解。
| 操作符 | 说明 | 实例 |
|---|---|---|
| . | 表示任何单个字符 | |
| [] | 字符集,对单个字符给出取值范围 | [ab]表示a、b,[a-z]表示a到z单个字符 |
| [^] | 非字符集,对单个字符给出排除范围 | [^ab]表示非a或b的单个字符 |
| * | 前一个字符0次或无限次扩展贪婪匹配 | ab*表示a、abb等 |
| + | 前一个字符1次或无限次扩展贪婪匹配 | ab+表示abb、abbb等等 |
| ? | 前一个字符0次或1次扩展贪婪匹配 | ab?表示a、ab |
| | | 左右表达式任意一个 | abc|efg表示abc、efg |
| {m} | 扩展前一个字符m次 | ab{2}c表示abbc |
| {m,n} | 扩展前一个字符[m,n]次 | ab{1,2}c表示abc、abbc |
| ^ | 匹配字符串开头 | ^abc表示abc且只匹配开头abcvvvabc |
| $ | 匹配字符串结尾 | abc$表示abc且只匹配开头abcvvvabc |
| () | 分组标记,内部只能使用**|**操作符 | (abc|efg)表示abc、efg |
| \d | 数字,等价于 [0-9] | |
| \w | 单词字符,等价于 [A-Za-z0-9_] |
下面是一些经典正则表达式
| 经典正则表达式 | 说明 |
|---|---|
| ^[A-za-z]+$ | 由26个字母组成的字符串 |
| ^[A-za-z0-9]+$ | 由26个字母和数字组成的字符串 |
| ^-?\d+$ | 整数形式的字符串 |
| ^[0-9][0-9]*[0-9]$ | 可由0开头的正整数形式的字符串 |
| ^[1-9][0-9]*[0-9]$ | 不可由0开头的正整数形式的字符串 |
| [1-9]\d{5} | 中国境内邮政编码 |
| [\u4e00-\u9fa5] | 匹配单个中文字符 |
小测验?
我们来看看如何匹配一个ip地址呢?
首先ip的每一个段范围为[ 0 , 255 ] [0,255][0,255]并且段与段之间由 . 分割。那么难点在于如何表示[ 0 − 255 ] [0-255][0−255]
可以将[ 0 , 255 ] [0,255][0,255]分为[ 0 − 99 ] [0-99][0−99]、[ 100 − 199 ] [100-199][100−199]、[ 200 − 249 ] [200-249][200−249]、[ 250 − 255 ] [250-255][250−255]
对应的正则表达式为 (([1-9]?\d)|1\d{2}|2[0-4]\d|25[0-5])
而每个段都有 . 分割可优化为 ((([1-9]?\d)|1\d{2}|2[0-4]\d|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])

贪婪匹配与非贪婪匹配
那么这个小标题的实例是啥呢?在写一个关于定向爬取某宝商品信息的spider时遇到了一个非贪婪匹配的应用在这里进行讲解?
首先来看看非贪婪匹配也叫最小匹配的操作符有哪些吧
(>人<;)
| 操作符 | 说明 |
|---|---|
| *? | 前一个字符0次或无限次扩展非贪婪匹配 |
| +? | 前一个字符1次或无限次扩展非贪婪匹配 |
| ?? | 前一个字符0次或1次扩展非贪婪匹配 |
| {m,n}? | 扩展前一个字符[m,n]次非贪婪匹配 |
根据request.get()来的html文档来看我们需要爬取的数据发现商品价格是 “view_price”:“139.00” 的格式,而商品名是 “raw_title”:“欧格双肩包男士背包可扩容大容量出差旅行李” 的格式。这里真的不是❌广告哦o(*▽*)q
那么如何在如此多的数据中将其提取出来呢?就要用到非贪婪匹配了。
我随意截取返回来的数据进行实例讲解

可以看到第一种正则表达式会一直匹配到最后一个双引号,也就是所谓的贪婪匹配,总会选取长度教长的结果返回。
而第二种正则表达式就是我们想要的结果了,会选取长度教小的结果返回,也就是所谓的非贪婪匹配了。
下面放上定向爬取某宝商品信息的spider 。
import requests
import re
def getHtmlText(url):
try:
headers = \
{
'user-agent': '',
'cookie': ''
}
r = requests.get(url, headers = headers,timeout = 30)
r.raise_for_status()
r.encoding = r.apparent_encoding
return r.text
except:
print("获取失败")
return ""
def fillList(htmlText, uinfo):
try:
prices = re.findall(r'\"view_price\":\"[\d\.]*\"', htmlText)
titles = re.findall(r'\"raw_title\":\".*?\"', htmlText)
for i in range(len(prices)):
price = eval(prices[i].split(':')[1])
title = eval(titles[i].split(':')[1])
uinfo.append([price, title])
except:
print('获取失败')
def printList(uinfo):
tplt = "{:4}\t{:8}\t{:16}"
print(tplt.format("序号", "价格", "商品名称"))
count = 0
for i in uinfo:
count += 1
print(tplt.format(count, i[0], i[1]))
def main():
goods = input('请输入需要查询的商品:')
url = 'https://s.taobao.com/search?q=' + goods
depth = 2
uinfo = []
for i in range(depth):
try:
searchUrl = url + '&s=' + str(44 * i)
htmlText = getHtmlText(searchUrl)
fillList(htmlText, uinfo)
except:
continue
printList(uinfo)
main()
而在实现中遇到了一个坑,如果直接用request请求来的文档数据与在浏览器中查看的文档数据不一致。
可能是因为查看页面源代码只是看到的网页初始状态,但实际上网页在加载完成后可能立即就会执行JavaScript 并改变了初始状态。现在的网页不同于传统的动态网页,能在不刷新网页的前提下改变网页局部的数据,这一切都是通过JavaScript和服务器交互进行的。
因此需要启动开发者模式F12寻找发出的Request Headers并修改user-agent和cookie与浏览器相同。