《零基础入门学习Python》第060讲:论一只爬虫的自我修养8:正则表达式4

有了前面几节课的准备,我们这一次终于可以真刀真枪的干一场大的了,但是呢,在进行实战之前,我们还要讲讲正则表达式的实用方法和扩展语法,然后再来实战,大家多把持一会啊。

我们先来翻一下文档:

首先,我们要举的例子是讲得最多的 search() 方法,search() 方法 既有模块级别的,就是直接调用 re.search() 来实现,另外,编译后的正则表达式模式对象也同样拥有 search() 方法,我问问大家,它们之间有区别吗?

如果你的回答仅仅是 模块级别的search() 方法比模式级别的search() 方法要多一个正则表达式的参数,那你肯定没有去翻文档。

re.``search(pattern , string , flags=0)

Scan through string looking for the first location where the regular expression pattern produces a match, and return a corresponding match object. Return None if no position in the string matches the pattern; note that this is different from finding a zero-length match at some point in the string.

这是模块级别的 search() 方法,大家注意它的参数,它有一个 flags 参数, flags 参数就我们上节课讲得 编译标志 位,作为一个模块级别的,它没办法复印,它直接在这里使用它的标志位就可以了。

pattern 是正则表达式的模式

string 是要搜索的字符串

我们再来看一下 如果是编译后的模式对象,它的 search() 方法又有哪些参数:

regex.``search(string , *pos* \[, *endpos*])

Scan through string looking for the first location where this regular expression produces a match, and return a corresponding match object. Return None if no position in the string matches the pattern; note that this is different from finding a zero-length match at some point in the string.

The optional second parameter pos gives an index in the string where the search is to start; it defaults to 0. This is not completely equivalent to slicing the string; the '^' pattern character matches at the real beginning of the string and at positions just after a newline, but not necessarily at the index where the search is to start.

The optional parameter endpos limits how far the string will be searched; it will be as if the string is endpos characters long, so only the characters from pos to endpos - 1 will be searched for a match. If endpos is less than pos , no match will be found; otherwise, if rx is a compiled regular expression object, rx.search(string, 0, 50) is equivalent to rx.search(string[:50], 0).

前面的 pattern, 模式对象的参数,就不需要了。

string 第一个参数就是待搜索的字符串

后面有两个可选参数是我们模块级别的 search() 方法没有的,它分别代表需要搜索的起始位置(pos)和结束位置(endpos)

你就可以像 rx.search(string, 0, 50) 或者 rx.search(string[:50], 0) 这样子去匹配它的搜索位置了。

还有一点可能被忽略的就是,search() 方法并不会立刻返回你所需要的字符串,取而代之,它是返回一个匹配对象。我们来举个例子:

复制代码
  1. >>> import re

  2. >>> result = re.search(r" (\w+) (\w+)", "I love Python.com")

  3. >>> result

  4. <_sre.SRE_Match object; span=(1, 13), match=' love Python'>

我们看到,这个 result 是一个匹配对象( match object.),而不是一个字符串。它这个匹配对象有一些方法,你使用这些方法才能够获得你所需要的匹配的字符串:

例如:group()方法:

复制代码
  1. >>> result.group()

  2. ' love Python'

我们就把匹配的内容打印出来了。首先是 一个空格,然后是 \w+ ,就是任何字符,这里就是love,然后又是一个空格,然后又是 \w+,这里就是Python。

说到这个 group()方法,值的一提的是,如果正则表达式中存在着 子组,子组会将匹配的内容进行 捕获,通过这个 group()方法 中设置序号,可以提取到对应的 子组(序号从1开始) 捕获的字符串。例如:

复制代码
  1. >>> result.group(1)

  2. 'love'

  3. >>> result.group(2)

  4. 'Python'

除了 group()方法 之外,它还有 start()方法 、end()方法、 span() 方法,分别返回它匹配的开始位置、结束位置、范围。

match.``start(*group*)

match.``end(*group*)

Return the indices of the start and end of the substring matched by group ; group defaults to zero (meaning the whole matched substring). Return -1 if group exists but did not contribute to the match. For a match object m , and a group g that did contribute to the match, the substring matched by group g (equivalent to m.group(g)) is

复制代码
m.string[m.start(g):m.end(g)]

Note that m.start(group) will equal m.end(group) if group matched a null string. For example, after m = re.search('b(c?)', 'cba'), m.start(0) is 1, m.end(0) is 2, m.start(1) and m.end(1) are both 2, and m.start(2) raises an IndexError exception.

An example that will remove remove_this from email addresses:

复制代码
>>> email = "tony@tiremove_thisger.net"
>>> m = re.search("remove_this", email)
>>> email[:m.start()] + email[m.end():]
'tony@tiger.net'

match.``span(*group*)

For a match m , return the 2-tuple (m.start(group), m.end(group)). Note that if group did not contribute to the match, this is (-1, -1). group defaults to zero, the entire match.

复制代码
  1. >>> result.start()

  2. 1

  3. >>> result.end()

  4. 13

  5. >>> result.span()

  6. (1, 13)

接下来讲讲 findall() 方法:

re.``findall(pattern , string , flags=0)

Return all non-overlapping matches of pattern in string , as a list of strings. The string is scanned left-to-right, and matches are returned in the order found. If one or more groups are present in the pattern, return a list of groups; this will be a list of tuples if the pattern has more than one group. Empty matches are included in the result unless they touch the beginning of another match.

有人可能会觉得,findall() 方法很容易,不就是找到所有匹配的内容,然后把它们组织成列表的形式返回吗。

没错,这是在正则表达式里没有子组的情况下所做的事,如果正则表达式里包含了子组,那么,findall() 会变得很聪明。

我们来举个例子吧,上贴吧爬图:

例如我们想下载这个页面的所有图片:贴吧404

我们先来踩点,看到图片格式的标签:

我们就来直接写代码啦:

首先,我们写下下面的代码,来爬取 图片地址:

复制代码
  1. import re

  2. p = r'<img class="BDE_Image" src="[^"]+\.jpg"'

  3. imglist = re.findall(p, html)

  4. for each in imglist:

  5. print(each)

打印的结果为:

复制代码
  1. ============== RESTART: C:\Users\XiangyangDai\Desktop\tieba.py ==============

  2. <img class="BDE_Image" src="https://imgsa.baidu.com/forum/w%3D580/sign=65ac7c3d9e0a304e5222a0f2e1c9a7c3/4056053b5bb5c9ea8d7d0bdadc39b6003bf3b34e.jpg"

  3. <img class="BDE_Image" src="https://imgsa.baidu.com/forum/w%3D580/sign=d887aa03394e251fe2f7e4f09787c9c2/77f65db5c9ea15ceaf60e830bf003af33b87b24e.jpg"

  4. <img class="BDE_Image" src="https://imgsa.baidu.com/forum/w%3D580/sign=0db90d472c1f95caa6f592bef9167fc5/2f78cfea15ce36d34f8a8b0933f33a87e850b14e.jpg"

  5. <img class="BDE_Image" src="https://imgsa.baidu.com/forum/w%3D580/sign=abfd18169ccad1c8d0bbfc2f4f3f67c4/bd2713ce36d3d5392db307fa3387e950342ab04e.jpg"

很显然,这不是我们需要的地址,我们需要的只是后面的部分。我们接下来要解决的问题就是如何将里面的地址提取出来,不少人听到这里,可能就已经开始动手了。但是,别急,我这里有更好的方法。

只需要把图片地址用小括号括起来,即将:

p = r'<img class="BDE_Image" src="\^"+\.jpg"' 改为 p = r'<img class="BDE_Image" src="(\^"+\.jpg)"',

大家再来看一下运行后的结果:

复制代码
  1. ============== RESTART: C:\Users\XiangyangDai\Desktop\tieba.py ==============

  2. https://imgsa.baidu.com/forum/w%3D580/sign=65ac7c3d9e0a304e5222a0f2e1c9a7c3/4056053b5bb5c9ea8d7d0bdadc39b6003bf3b34e.jpg

  3. https://imgsa.baidu.com/forum/w%3D580/sign=d887aa03394e251fe2f7e4f09787c9c2/77f65db5c9ea15ceaf60e830bf003af33b87b24e.jpg

  4. https://imgsa.baidu.com/forum/w%3D580/sign=0db90d472c1f95caa6f592bef9167fc5/2f78cfea15ce36d34f8a8b0933f33a87e850b14e.jpg

  5. https://imgsa.baidu.com/forum/w%3D580/sign=abfd18169ccad1c8d0bbfc2f4f3f67c4/bd2713ce36d3d5392db307fa3387e950342ab04e.jpg

是不是很兴奋,是不是很惊讶,先别急,我先把代码敲完,再给大家讲解。

复制代码
  1. import urllib.request

  2. import re

  3. def open_url(url):

  4. req = urllib.request.Request(url)

  5. req.add_header('User-Agent', 'Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.65 Safari/537.36')

  6. response = urllib.request.urlopen(url)

  7. html = response.read()

  8. return html

  9. def get_img(url):

  10. html = open_url(url).decode('utf-8')

  11. p = r'<img class="BDE_Image" src="([^"]+\.jpg)"'

  12. imglist = re.findall(p, html)

  13. '''

  14. for each in imglist:

  15. print(each)

  16. '''

  17. for each in imglist:

  18. filename = each.split('/')[-1]

  19. urllib.request.urlretrieve(each, filename, None)

  20. if __name__ == '__main__':

  21. url = "https://tieba.baidu.com/p/4863860271"

  22. get_img(url)

运行结果,就是很多美眉图片出现在桌面了(前提是这个程序在桌面运行,图片自动下载到程序所在文件夹。)

接下来就来解决大家的困惑了:为什么加个小括号会如此方便呢?

这是因为在 findall() 方法中,如果给出的正则表达式是包含着子组的话,那么就会把子组的内容单独给返回回来。然而,如果存在多个子组,那么它还会将匹配的内容组合成元组的形式再返回。

我们还是举个例子:

因为有时候 findall() 如果使用的不好,很多同学就会感觉很疑惑,很迷茫......

拿前面匹配 ip 地址的正则表达式来讲解,我们使用 findall() 来尝试自动从https://www.xicidaili.com/wt/获取 ip 地址:

初代码如下:

复制代码
  1. import urllib.request

  2. import re

  3. def open_url(url):

  4. req = urllib.request.Request(url)

  5. req.add_header('User-Agent', 'Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.65 Safari/537.36')

  6. reponse = urllib.request.urlopen(req)

  7. html = reponse.read()

  8. return html

  9. def get_ip(url):

  10. html = open_url(url).decode('utf-8')

  11. p = r'(([0,1]?\d?\d|2[0-4]\d|25[0-5])\.){3}([0,1]?\d?\d|2[0-4]\d|25[0-5])'

  12. iplist = re.findall(p, html)

  13. for each in iplist:

  14. print(each)

  15. if __name__ == "__main__":

  16. url = "https://www.xicidaili.com/wt/"

  17. get_ip(url)

运行结果如下:

复制代码
  1. ============== RESTART: C:\Users\XiangyangDai\Desktop\getIP.py ==============

  2. ('180.', '180', '122')

  3. ('248.', '248', '79')

  4. ('129.', '129', '198')

  5. ('217.', '217', '7')

  6. ('40.', '40', '35')

  7. ('128.', '128', '21')

  8. ('118.', '118', '106')

  9. ('101.', '101', '46')

  10. ('3.', '3', '4')

得到的结果让我们很迷茫,为什么会这样呢?这明显不是我们想要的结果,这是因为我们在正则表达式里面使用了 3 个子组,所以,findall() 会自作聪明的把我们的结果做了分类,然后用 元组的形式返回给我们。

那有没有解决的方法呢?

要解决这个问题,我们可以让子组不捕获内容。

我们查看 -> Python3 正则表达式特殊符号及用法(详细列表),寻求扩展语法。

让子组不捕获内容,扩展语法 就是非捕获组:

所以我们的初代码修改如下:

复制代码
  1. import urllib.request

  2. import re

  3. def open_url(url):

  4. req = urllib.request.Request(url)

  5. req.add_header('User-Agent', 'Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.65 Safari/537.36')

  6. reponse = urllib.request.urlopen(req)

  7. html = reponse.read()

  8. return html

  9. def get_ip(url):

  10. html = open_url(url).decode('utf-8')

  11. p = r'(?:(?:[0,1]?\d?\d|2[0-4]\d|25[0-5])\.){3}(?:[0,1]?\d?\d|2[0-4]\d|25[0-5])'

  12. iplist = re.findall(p, html)

  13. for each in iplist:

  14. print(each)

  15. if __name__ == "__main__":

  16. url = "https://www.xicidaili.com/wt/"

  17. get_ip(url)

运行得到的结果也是我们想要的 ip 地址了,如下:

复制代码
  1. ============== RESTART: C:\Users\XiangyangDai\Desktop\getIP.py ==============

  2. 183.47.40.35

  3. 61.135.217.7

  4. 221.214.180.122

  5. 101.76.248.79

  6. 182.88.129.198

  7. 175.165.128.21

  8. 42.48.118.106

  9. 60.216.101.46

  10. 219.245.3.4

  11. 117.85.221.45

接下来我们又回到文档:

另外还有一些使用的方法,例如:

finditer() ,是将结果返回一个迭代器,方便以迭代方式获取数据。

sub() ,是实现替换的操作。

在Python3 正则表达式特殊符号及用法(详细列表)中也还有一些特殊的语法,例如:

(?=...):前向肯定断言。

(?!...):前向否定断言。

(?<=...):后向肯定断言。

(?<!...):后向肯定断言。

这些都是非常有用的,但是呢,这些内容有点多了,如果说全部都讲正则表达式的话,那我们就是喧宾夺主了,我们主要讲的是 网络爬虫 哦。

所以,大家还是要自主学习一下,多看,多学,多操作。

相关推荐
用户0190275816119 分钟前
如何用 Python 回测肯特纳通道(Keltner Channel)突破策略?(EMA+ATR 通道)
python
老歌老听老掉牙32 分钟前
基于Frenet-Serret框架的自然轴系关系推导与SymPy验证
python·sympy·自然轴系
Java后端的Ai之路34 分钟前
Python 进阶探索25 - difflib模块之文本对比
开发语言·python·django·哈希算法·difflib
35 分钟前
一个天才写了三年Python,最后发现装饰器最有用的用法只有3个
python
全栈练习生36 分钟前
大模型原理之 MoE 混合专家模型
python·ai
askama0040 分钟前
Ubuntu安装Anaconda并配置环境
linux·python
计算机编程指导师1 小时前
【计算机毕设选题】基于Hadoop的电信网络诈骗话术语义特征挖掘分析系统源码 毕业设计 选题推荐 毕设选题 数据分析 机器学习
大数据·数据库·python
CS创新实验室1 小时前
Python 的鸭子类型
开发语言·windows·python
ThinkerQAQ_1 小时前
并发编程(七):volatile——从语言规则到 CPU
java·python·go
DanCheng-studio1 小时前
毕业设计项目 基于深度学习的图像修复算法 DCGAN
python·毕业设计·毕设