在 正则表达式 - 匹配开头、结尾、中间 - 某天气网站网页源代码分析 这篇文章里,我们介绍了如何用正则表达式匹配包含特定样式的Table标签,也就是同时匹配开头、结尾、以及中间。
当你能真正理解这个写法,就会觉得不过是柳暗花明罢了。是的,现实往往如此 —— 屡败屡战的过程最难将书,结果最易轻描淡写,只剩下予取予求的招数变化。
所以接下来,我们要做的就是复制这个成功经验,把之前那些“2步走方案”实现的匹配替换,全部改为“1步到位”方案 —— 性能且不说,我们先追求代码简洁度,开搞。
regex101 网站有提供1个对理解正则表达式运作过程非常有用的工具:Regex Debugger。我简单录了个视频,有兴趣的同学可以自己去研究。
正则表达式运行过程
这里想表达的是,我们可能不需要理解正则的原理,就向开车没必要懂车构造一样。但有一点需要清楚的是:匹配就是寻找,在小范围内寻找一定比在大范围内寻找要快。
基于这个铁律,看下我们的代码逻辑,即使是在剔除“假Table”后,几乎每个正则也还是在做全文匹配,寻找 td,寻找 th 等等等等 —— 而我们的目标范围其实很明确,就是在剩余的这个 “真Table” 中,所以其实没有必要对 html 内 table 外 的其他任何内容去做任何匹配了。
我们用1个正则将“真Table” ——
# limit match scope, table to be matched
regex_tbm_table = r"([\s\S]*?)<\/tbody>"
tbm_table = re.findall(regex_tbm_table, page_source)[0]
###
# 中间是对这个 “真table” 的进一步处理
###
# 将处理好的 table 再填充回 html 代码
page_source = re.sub(regex_tbm_table, tbm_table, page_source)
鸟枪换炮 重装出击
这篇 爬虫攻守道 - 2023最新 - Python Selenium 实现 - 数据去伪存真,正则表达式谁与争锋 - 爬取某天气网站历史数据 留下的第1条 Todo ,是我们的第1个目标。
之前为了替换掉包含4种隐藏样式的td,我们先是正则得到td,然后for 循环全部 td,再次正则找出匹配然后 replace 为空。忽略打印测试,我们用了21行代码来达到这个目标。
# 在保留的 table 中匹配出所有的 td
regex_td = r""
tds = re.findall(regex_td, page_source)
print('{}_{}: total {} tds been found'.format(city, month, len(tds)))
# 将包含 display:none 隐藏样式的 td 其实就是假数据,替换为空
# display:none 是最终效果,实际在代码里是随机字符串,而且好几个,所以先找到 css 定义中内容为 display:none 的样式名称
regex_td_css1 = r"(.*?)\s*{\n\s*display: none;"
class_ndisplays = re.findall(regex_td_css1, page_source)
# print('class_ndisplays', len(class_ndisplays))
# 将包含这些样式的 td 替换为空
for class_ndisplay in class_ndisplays:
class_ndisplay = class_ndisplay.replace('.', '').strip()
for td in tds:
if re.findall(class_ndisplay, td):
page_source = page_source.replace(td, '')
# 将包含明文 display:none 样式的 td 替换为空
regex_td_css2 = r"display:none"
for td in tds:
if re.findall(regex_td_css2, td):
page_source = page_source.replace(td, '')
# 将包含 hidden-lg 隐藏样式的 td 其实就是假数据,替换为空
regex_td_css3 = "hidden-lg"
for td in tds:
if re.findall(regex_td_css3, td):
page_source = page_source.replace(td, '')
regex_td_css4 = r"\"hidden\""
for td in tds:
if re.findall(regex_td_css4, td):
page_source = page_source.replace(td, '')
让我们看看 21行最终能简化到几行。
第1步:先打出头鸟。
比其他3个样式都复杂的 regex_td_css1,原先的处理逻辑是先根据 css定义的形式 —— {}包裹,以及内容为 display: none 的 —— 反向提取得到名称。然后再到td 范围内匹配 css引用名称,进而替换。
以我目前极为有限的前端知识 —— 提取名称这步是绕不开的,但是css 引用都是定义在 标签内的。所以按照缩小范围精准打击的思路,我们先得到 标签内的东西,代号为 tbm_style,然后在这个小范围内得到被引用的 css样式名称,依然是1个随机字符串列表。
再下来,我们用1次 for 循环,在 tbm_table 范围内把这些名称全部替换为 和 regex_td_css2 一致的明文 display:none。 经过这样“合并”处理后,即使还需要 for 循环全部 td,我们也只需要3次 (针对 regex_td_css2,regex_td_css3,regex_td_css4)。于是得到这样的代码:
# 处理 regex_td_css1
# css 引用样式在 中定义
regex_tbm_style = r" 中定义
regex_tbm_style = r"