|
|
马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
x
引言
在当今数据驱动的时代,网络爬虫已成为获取数据的重要手段。Python作为最受欢迎的爬虫编程语言,其强大的库支持和简洁的语法使得爬虫开发变得高效而简单。而在Python爬虫技术栈中,正则表达式(Regular Expression)是一项不可或缺的技能,它能帮助我们高效地从复杂的网页文本中提取所需数据。
正则表达式是一种强大的文本匹配工具,通过特定的模式匹配字符串,实现数据的快速查找、替换和提取。对于爬虫开发者来说,掌握正则表达式意味着能够更加灵活地处理各种网页结构,提取出有价值的信息。
本文将从正则表达式的基础语法讲起,逐步深入到Python中的实际应用,并通过丰富的实战案例,帮助读者全面掌握正则表达式在爬虫数据提取中的应用技巧。
正则表达式基础语法
什么是正则表达式
正则表达式是一种特殊的字符串模式,用于描述一类字符串的共同特征。它通过一系列特定字符和符号组成,用来检查一个字符串是否含有某种子字符串、将匹配的子字符串替换或者从某个字符串中取出符合某个条件的子字符串等。
基本元字符
元字符是正则表达式中具有特殊含义的字符,它们不表示自身,而是用于构建匹配模式。以下是最常用的元字符:
• .:匹配除换行符以外的任意单个字符
• ^:匹配字符串的开始位置
• $:匹配字符串的结束位置
• *:匹配前面的子表达式零次或多次
• +:匹配前面的子表达式一次或多次
• ?:匹配前面的子表达式零次或一次
• {n}:匹配前面的子表达式恰好n次
• {n,}:匹配前面的子表达式至少n次
• {n,m}:匹配前面的子表达式至少n次,至多m次
字符类
字符类用于匹配一组字符中的任意一个,使用方括号[]表示:
• [abc]:匹配a、b或c中的任意一个字符
• [^abc]:匹配除了a、b、c之外的任意字符
• [a-z]:匹配任意小写字母
• [A-Z]:匹配任意大写字母
• [0-9]:匹配任意数字
• [a-zA-Z0-9]:匹配任意字母或数字
特殊字符类
正则表达式提供了一些预定义的字符类,用于表示常见的字符集合:
• \d:匹配任意数字,相当于[0-9]
• \D:匹配任意非数字字符,相当于[^0-9]
• \w:匹配任意字母、数字或下划线,相当于[a-zA-Z0-9_]
• \W:匹配任意非字母、数字或下划线的字符,相当于[^a-zA-Z0-9_]
• \s:匹配任意空白字符(空格、制表符、换行符等)
• \S:匹配任意非空白字符
分组和捕获
使用圆括号()可以对正则表达式进行分组,并且可以捕获匹配的内容:
• (pattern):匹配pattern并捕获匹配的文本
• (?:pattern):匹配pattern但不捕获匹配的文本
• (?=pattern):正向预查,匹配pattern前面的位置
• (?!pattern):负向预查,匹配不在pattern前面的位置
选择符
使用竖线|表示选择符,用于匹配多个可能的模式之一:
• a|b:匹配a或b
• (abc)|(def):匹配abc或def
转义字符
如果要匹配正则表达式中的特殊字符本身,需要使用反斜杠\进行转义:
• \.:匹配点字符
• \\:匹配反斜杠字符
• \*:匹配星号字符
Python中的re模块
Python提供了re模块来支持正则表达式操作,使用前需要先导入该模块:
主要函数和方法
re.match()尝试从字符串的起始位置匹配一个模式,如果匹配成功,返回一个匹配对象;否则返回None。
- import re
- pattern = r'Python'
- text = 'Python is a popular programming language'
- result = re.match(pattern, text)
- if result:
- print('匹配成功:', result.group())
- else:
- print('匹配失败')
复制代码
输出:
re.search()扫描整个字符串,返回第一个成功的匹配。如果匹配成功,返回一个匹配对象;否则返回None。
- import re
- pattern = r'programming'
- text = 'Python is a popular programming language'
- result = re.search(pattern, text)
- if result:
- print('匹配成功:', result.group())
- else:
- print('匹配失败')
复制代码
输出:
re.findall()查找字符串中所有匹配的子串,并返回一个列表。
- import re
- pattern = r'\d+' # 匹配一个或多个数字
- text = 'There are 123 apples and 456 oranges'
- result = re.findall(pattern, text)
- print('所有匹配:', result)
复制代码
输出:
re.finditer()与re.findall()类似,但返回的是一个迭代器,每个元素是一个匹配对象。
- import re
- pattern = r'\d+' # 匹配一个或多个数字
- text = 'There are 123 apples and 456 oranges'
- result = re.finditer(pattern, text)
- for match in result:
- print(f'找到数字: {match.group()}, 位置: {match.span()}')
复制代码
输出:
- 找到数字: 123, 位置: (10, 13)
- 找到数字: 456, 位置: (23, 26)
复制代码
re.sub()用于替换字符串中的匹配项。
- import re
- pattern = r'\d+' # 匹配一个或多个数字
- text = 'There are 123 apples and 456 oranges'
- result = re.sub(pattern, 'number', text)
- print('替换结果:', result)
复制代码
输出:
- 替换结果: There are number apples and number oranges
复制代码
re.split()根据匹配的子串分割字符串。
- import re
- pattern = r'\s+' # 匹配一个或多个空白字符
- text = 'Python is a popular programming language'
- result = re.split(pattern, text)
- print('分割结果:', result)
复制代码
输出:
- 分割结果: ['Python', 'is', 'a', 'popular', 'programming', 'language']
复制代码
编译正则表达式
如果需要多次使用同一个正则表达式,可以使用re.compile()将其编译成一个正则表达式对象,这样可以提高效率。
- import re
- # 编译正则表达式
- pattern = re.compile(r'\d+')
- # 使用编译后的正则表达式对象进行匹配
- text1 = 'There are 123 apples'
- text2 = 'There are 456 oranges'
- result1 = pattern.search(text1)
- result2 = pattern.search(text2)
- print(result1.group())
- print(result2.group())
复制代码
输出:
匹配对象的方法
当正则表达式匹配成功时,会返回一个匹配对象,该对象有以下常用方法:
• group():返回整个匹配的字符串
• group(n):返回第n个分组匹配的字符串
• groups():返回所有分组匹配的字符串组成的元组
• start():返回匹配的起始位置
• end():返回匹配的结束位置
• span():返回一个包含匹配起始和结束位置的元组
- import re
- pattern = r'(\w+) (\d+)' # 匹配单词和数字
- text = 'Python 3 is released in 2008'
- result = re.search(pattern, text)
- if result:
- print('整个匹配:', result.group())
- print('第一个分组:', result.group(1))
- print('第二个分组:', result.group(2))
- print('所有分组:', result.groups())
- print('匹配位置:', result.span())
复制代码
输出:
- 整个匹配: Python 3
- 第一个分组: Python
- 第二个分组: 3
- 所有分组: ('Python', '3')
- 匹配位置: (0, 7)
复制代码
标志参数
正则表达式函数可以接受标志参数,用于修改匹配的行为。常用的标志有:
• re.I或re.IGNORECASE:忽略大小写
• re.M或re.MULTILINE:多行模式,影响^和$
• re.S或re.DOTALL:使.匹配包括换行符在内的所有字符
• re.X或re.VERBOSE:忽略正则表达式中的空白和注释,使其更易读
- import re
- pattern = r'python'
- text = 'Python is a popular programming language'
- # 不使用re.IGNORECASE
- result1 = re.search(pattern, text)
- print('不忽略大小写:', result1)
- # 使用re.IGNORECASE
- result2 = re.search(pattern, text, re.IGNORECASE)
- print('忽略大小写:', result2.group())
复制代码
输出:
- 不忽略大小写: None
- 忽略大小写: Python
复制代码
网页内容提取
从HTML中提取数据
在爬虫开发中,我们经常需要从HTML文档中提取数据。正则表达式可以很好地处理这类任务。下面是一个简单的例子:
- import re
- import requests
- # 获取网页内容
- url = 'https://example.com'
- response = requests.get(url)
- html = response.text
- # 提取所有链接
- link_pattern = r'<a[^>]+href="([^"]*)"[^>]*>(.*?)</a>'
- links = re.findall(link_pattern, html)
- for link, text in links:
- print(f'链接: {link}, 文本: {text}')
复制代码
提取特定标签内容
如果我们需要提取特定HTML标签的内容,可以使用以下方法:
- import re
- html = '''
- <html>
- <head>
- <title>示例页面</title>
- </head>
- <body>
- <h1>欢迎来到示例页面</h1>
- <div class="content">
- <p>这是一个段落。</p>
- <p>这是另一个段落。</p>
- </div>
- </body>
- </html>
- '''
- # 提取标题
- title_pattern = r'<title>(.*?)</title>'
- title_match = re.search(title_pattern, html)
- if title_match:
- print('页面标题:', title_match.group(1))
- # 提取所有段落
- paragraph_pattern = r'<p>(.*?)</p>'
- paragraphs = re.findall(paragraph_pattern, html)
- print('所有段落:')
- for i, p in enumerate(paragraphs, 1):
- print(f'{i}. {p}')
复制代码
输出:
- 页面标题: 示例页面
- 所有段落:
- 1. 这是一个段落。
- 2. 这是另一个段落。
复制代码
处理属性值
有时我们需要提取HTML标签的属性值,例如class、id等:
- import re
- html = '''
- <div class="article" id="post-123">
- <h2 class="title">文章标题</h2>
- <p class="content">文章内容...</p>
- </div>
- '''
- # 提取div的class和id
- div_pattern = r'<div class="([^"]*)" id="([^"]*)">'
- div_match = re.search(div_pattern, html)
- if div_match:
- print('div class:', div_match.group(1))
- print('div id:', div_match.group(2))
- # 提取所有标签的class
- class_pattern = r'class="([^"]*)"'
- classes = re.findall(class_pattern, html)
- print('所有class:', classes)
复制代码
输出:
- div class: article
- div id: post-123
- 所有class: ['article', 'title', 'content']
复制代码
处理嵌套结构
处理嵌套的HTML结构是正则表达式的一个挑战,因为正则表达式本身不适合处理递归结构。但对于简单的嵌套,我们可以使用以下方法:
- import re
- html = '''
- <div class="outer">
- <p>外层内容</p>
- <div class="inner">
- <p>内层内容</p>
- </div>
- <p>更多外层内容</p>
- </div>
- '''
- # 提取最外层div的内容
- outer_div_pattern = r'<div class="outer">(.*?)</div>'
- outer_div_match = re.search(outer_div_pattern, html, re.DOTALL)
- if outer_div_match:
- print('外层div内容:', outer_div_match.group(1))
- # 提取内层div的内容
- inner_div_pattern = r'<div class="inner">(.*?)</div>'
- inner_div_match = re.search(inner_div_pattern, html, re.DOTALL)
- if inner_div_match:
- print('内层div内容:', inner_div_match.group(1))
复制代码
输出:
- 外层div内容:
- <p>外层内容</p>
- <div class="inner">
- <p>内层内容</p>
- </div>
- <p>更多外层内容</p>
- 内层div内容:
- <p>内层内容</p>
复制代码
实战案例
案例1:提取网页中的所有图片链接
- import re
- import requests
- from urllib.parse import urljoin
- def extract_image_links(url):
- # 获取网页内容
- try:
- response = requests.get(url)
- html = response.text
- except Exception as e:
- print(f"获取网页失败: {e}")
- return []
-
- # 提取所有图片链接
- img_pattern = r'<img[^>]+src="([^"]*)"[^>]*>'
- img_links = re.findall(img_pattern, html)
-
- # 将相对URL转换为绝对URL
- absolute_img_links = [urljoin(url, img) for img in img_links]
-
- return absolute_img_links
- # 使用示例
- url = 'https://example.com'
- image_links = extract_image_links(url)
- print(f"找到 {len(image_links)} 张图片:")
- for i, link in enumerate(image_links, 1):
- print(f"{i}. {link}")
复制代码
案例2:提取新闻文章的标题和内容
- import re
- import requests
- def extract_news_article(url):
- # 获取网页内容
- try:
- response = requests.get(url)
- html = response.text
- except Exception as e:
- print(f"获取网页失败: {e}")
- return None, None
-
- # 提取标题
- title_pattern = r'<h1[^>]*>(.*?)</h1>'
- title_match = re.search(title_pattern, html)
- title = title_match.group(1) if title_match else "未找到标题"
-
- # 提取文章内容(假设内容在div class="article-content"中)
- content_pattern = r'<div class="article-content">(.*?)</div>'
- content_match = re.search(content_pattern, html, re.DOTALL)
- if content_match:
- # 移除HTML标签,只保留文本
- content = re.sub(r'<[^>]+>', '', content_match.group(1))
- # 清理多余的空白字符
- content = re.sub(r'\s+', ' ', content).strip()
- else:
- content = "未找到内容"
-
- return title, content
- # 使用示例
- url = 'https://example-news.com/article/123'
- title, content = extract_news_article(url)
- print(f"标题: {title}")
- print(f"内容: {content[:200]}...") # 只打印前200个字符
复制代码
案例3:抓取商品信息
- import re
- import requests
- import json
- def scrape_product_info(url):
- # 获取网页内容
- try:
- headers = {
- 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
- }
- response = requests.get(url, headers=headers)
- html = response.text
- except Exception as e:
- print(f"获取网页失败: {e}")
- return None
-
- # 提取商品名称
- name_pattern = r'<h1[^>]*class="product-name"[^>]*>(.*?)</h1>'
- name_match = re.search(name_pattern, html)
- product_name = name_match.group(1) if name_match else "未知商品"
-
- # 提取商品价格
- price_pattern = r'<span[^>]*class="price"[^>]*>(.*?)</span>'
- price_match = re.search(price_pattern, html)
- product_price = price_match.group(1) if price_match else "价格未知"
-
- # 提取商品评分
- rating_pattern = r'<span[^>]*class="rating"[^>]*>(.*?)</span>'
- rating_match = re.search(rating_pattern, html)
- product_rating = rating_match.group(1) if rating_match else "无评分"
-
- # 提取商品描述
- description_pattern = r'<div[^>]*class="product-description"[^>]*>(.*?)</div>'
- description_match = re.search(description_pattern, html, re.DOTALL)
- if description_match:
- # 移除HTML标签
- product_description = re.sub(r'<[^>]+>', '', description_match.group(1))
- # 清理多余的空白字符
- product_description = re.sub(r'\s+', ' ', product_description).strip()
- else:
- product_description = "无描述"
-
- # 构建商品信息字典
- product_info = {
- 'name': product_name,
- 'price': product_price,
- 'rating': product_rating,
- 'description': product_description
- }
-
- return product_info
- # 使用示例
- url = 'https://example-shop.com/product/123'
- product_info = scrape_product_info(url)
- if product_info:
- print(json.dumps(product_info, indent=2, ensure_ascii=False))
复制代码
案例4:提取社交媒体数据
- import re
- import requests
- def extract_social_media_posts(url):
- # 获取网页内容
- try:
- headers = {
- 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
- }
- response = requests.get(url, headers=headers)
- html = response.text
- except Exception as e:
- print(f"获取网页失败: {e}")
- return []
-
- # 提取所有帖子
- posts = []
-
- # 假设每个帖子都在一个div class="post"中
- post_pattern = r'<div[^>]*class="post"[^>]*>(.*?)</div>\s*(?=</div>|$)'
- post_matches = re.findall(post_pattern, html, re.DOTALL)
-
- for post_html in post_matches:
- # 提取作者
- author_pattern = r'<a[^>]*class="author"[^>]*>(.*?)</a>'
- author_match = re.search(author_pattern, post_html)
- author = author_match.group(1) if author_match else "未知作者"
-
- # 提取时间
- time_pattern = r'<time[^>]*>(.*?)</time>'
- time_match = re.search(time_pattern, post_html)
- post_time = time_match.group(1) if time_match else "未知时间"
-
- # 提取内容
- content_pattern = r'<div[^>]*class="content"[^>]*>(.*?)</div>'
- content_match = re.search(content_pattern, post_html, re.DOTALL)
- if content_match:
- # 移除HTML标签
- content = re.sub(r'<[^>]+>', '', content_match.group(1))
- # 清理多余的空白字符
- content = re.sub(r'\s+', ' ', content).strip()
- else:
- content = "无内容"
-
- # 提取点赞数
- likes_pattern = r'<span[^>]*class="likes"[^>]*>(\d+)</span>'
- likes_match = re.search(likes_pattern, post_html)
- likes = int(likes_match.group(1)) if likes_match else 0
-
- # 提取评论数
- comments_pattern = r'<span[^>]*class="comments"[^>]*>(\d+)</span>'
- comments_match = re.search(comments_pattern, post_html)
- comments = int(comments_match.group(1)) if comments_match else 0
-
- # 构建帖子信息字典
- post_info = {
- 'author': author,
- 'time': post_time,
- 'content': content,
- 'likes': likes,
- 'comments': comments
- }
-
- posts.append(post_info)
-
- return posts
- # 使用示例
- url = 'https://example-social-media.com/user/posts'
- posts = extract_social_media_posts(url)
- print(f"找到 {len(posts)} 条帖子:")
- for i, post in enumerate(posts, 1):
- print(f"\n帖子 {i}:")
- print(f"作者: {post['author']}")
- print(f"时间: {post['time']}")
- print(f"内容: {post['content'][:100]}...") # 只打印前100个字符
- print(f"点赞: {post['likes']}")
- print(f"评论: {post['comments']}")
复制代码
高级技巧
贪婪与非贪婪匹配
正则表达式默认是贪婪匹配的,即尽可能多地匹配字符。有时我们需要非贪婪匹配,即尽可能少地匹配字符。在量词后加上?可以使其变为非贪婪匹配。
- import re
- html = '''
- <div class="content">
- <p>第一段</p>
- <p>第二段</p>
- </div>
- <div class="footer">
- <p>页脚</p>
- </div>
- '''
- # 贪婪匹配:匹配到最后一个</div>
- greedy_pattern = r'<div class="content">(.*?)</div>'
- greedy_match = re.search(greedy_pattern, html, re.DOTALL)
- print("贪婪匹配结果:")
- print(greedy_match.group(1) if greedy_match else "无匹配")
- # 非贪婪匹配:匹配到第一个</div>
- non_greedy_pattern = r'<div class="content">(.*?)</div>'
- non_greedy_match = re.search(non_greedy_pattern, html, re.DOTALL)
- print("\n非贪婪匹配结果:")
- print(non_greedy_match.group(1) if non_greedy_match else "无匹配")
复制代码
前瞻和后顾
前瞻(lookahead)和后顾(lookbehind)是正则表达式的高级特性,用于匹配某些位置前后的内容,但不包含在匹配结果中。
- import re
- text = 'apple123 banana456 cherry789'
- # 正向预查:匹配后面跟着数字的单词
- pattern1 = r'\w+(?=\d)'
- matches1 = re.findall(pattern1, text)
- print("正向预查结果:", matches1)
- # 负向预查:匹配后面不跟着数字的单词
- pattern2 = r'\w+(?!\d)'
- matches2 = re.findall(pattern2, text)
- print("负向预查结果:", matches2)
- # 正向后顾:匹配前面是数字的单词
- pattern3 = r'(?<=\d)\w+'
- matches3 = re.findall(pattern3, text)
- print("正向后顾结果:", matches3)
- # 负向后顾:匹配前面不是数字的单词
- pattern4 = r'(?<!\d)\w+'
- matches4 = re.findall(pattern4, text)
- print("负向后顾结果:", matches4)
复制代码
使用回调函数处理匹配
re.sub()可以接受一个函数作为替换参数,该函数接收一个匹配对象,并返回替换字符串。
- import re
- def process_price(match):
- # 提取价格数值
- price = float(match.group(1))
- # 增加价格10%
- new_price = price * 1.1
- # 返回格式化后的价格
- return f'${new_price:.2f}'
- text = '商品A: $10.50, 商品B: $20.75, 商品C: $5.25'
- # 将所有价格增加10%
- new_text = re.sub(r'\$(\d+\.\d+)', process_price, text)
- print('原始文本:', text)
- print('处理后文本:', new_text)
复制代码
使用命名组
命名组可以为分组命名,使正则表达式更易读,并且可以通过名称而不是索引来访问分组。
- import re
- text = '2023-05-15'
- # 使用命名组
- pattern = r'(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})'
- match = re.search(pattern, text)
- if match:
- print('年:', match.group('year'))
- print('月:', match.group('month'))
- print('日:', match.group('day'))
-
- # 获取所有命名组
- print('所有命名组:', match.groupdict())
复制代码
编译正则表达式时添加注释
使用re.VERBOSE标志可以在正则表达式中添加注释,使其更易读。
- import re
- # 编译一个带有注释的正则表达式
- email_pattern = re.compile(r"""
- # 匹配用户名部分
- ([a-zA-Z0-9._%+-]+)
- # 匹配@符号
- @
- # 匹配域名部分
- ([a-zA-Z0-9.-]+)
- # 匹配点号
- \.
- # 匹配顶级域名
- ([a-zA-Z]{2,})
- """, re.VERBOSE)
- text = '联系邮箱: example@example.com'
- match = email_pattern.search(text)
- if match:
- print('完整邮箱:', match.group(0))
- print('用户名:', match.group(1))
- print('域名:', match.group(2))
- print('顶级域名:', match.group(3))
复制代码
最佳实践和性能优化
预编译正则表达式
如果同一个正则表达式需要多次使用,应该预编译它以提高性能。
- import re
- # 不好的做法:每次使用都重新编译
- def extract_emails_bad(text):
- emails = re.findall(r'[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}', text)
- return emails
- # 好的做法:预编译正则表达式
- EMAIL_PATTERN = re.compile(r'[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}')
- def extract_emails_good(text):
- emails = EMAIL_PATTERN.findall(text)
- return emails
复制代码
避免回溯问题
复杂的正则表达式可能导致大量的回溯,从而影响性能。以下是一些避免回溯问题的技巧:
1. 使用更具体的字符类,而不是.
2. 避免嵌套量词,如(a+)+
3. 使用原子组或占有量词(Python不支持,但可以通过其他方式模拟)
- import re
- # 可能导致回溯问题的正则表达式
- bad_pattern = r'^(a+)+$'
- # 优化后的正则表达式
- good_pattern = r'^a+$'
- text = 'a' * 100 # 100个a
- # 测试性能
- import timeit
- bad_time = timeit.timeit(lambda: re.match(bad_pattern, text), number=1000)
- good_time = timeit.timeit(lambda: re.match(good_pattern, text), number=1000)
- print(f'可能导致回溯的模式的执行时间: {bad_time:.6f}秒')
- print(f'优化后的模式的执行时间: {good_time:.6f}秒')
复制代码
使用合适的函数
根据需求选择合适的函数:
• 如果只需要检查是否存在匹配,使用re.search()而不是re.findall()
• 如果需要获取所有匹配,使用re.findall()或re.finditer()
• 如果只需要第一个匹配,使用re.search()而不是re.findall()
- import re
- text = 'Email: user@example.com, Phone: 123-456-7890'
- # 只需要检查是否存在邮箱
- if re.search(r'[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}', text):
- print('文本中包含邮箱')
- # 获取所有数字序列
- numbers = re.findall(r'\d+', text)
- print('所有数字序列:', numbers)
复制代码
避免捕获组
如果不需要捕获匹配的子串,使用非捕获组(?:...)可以提高性能。
- import re
- text = 'abc123def456ghi'
- # 使用捕获组
- pattern_with_capture = r'([a-z]+)(\d+)'
- matches_with_capture = re.findall(pattern_with_capture, text)
- print('使用捕获组的结果:', matches_with_capture)
- # 使用非捕获组
- pattern_without_capture = r'(?:[a-z]+)(\d+)'
- matches_without_capture = re.findall(pattern_without_capture, text)
- print('使用非捕获组的结果:', matches_without_capture)
复制代码
使用字符串方法处理简单模式
对于简单的模式,使用字符串方法可能比正则表达式更高效。
- import re
- import timeit
- text = 'This is a sample text with some words.'
- # 使用正则表达式检查是否包含单词
- def contains_word_regex(word):
- return bool(re.search(r'\b' + re.escape(word) + r'\b', text))
- # 使用字符串方法检查是否包含单词
- def contains_word_str(word):
- return word in text.split()
- # 测试性能
- word = 'sample'
- regex_time = timeit.timeit(lambda: contains_word_regex(word), number=10000)
- str_time = timeit.timeit(lambda: contains_word_str(word), number=10000)
- print(f'正则表达式方法的执行时间: {regex_time:.6f}秒')
- print(f'字符串方法的执行时间: {str_time:.6f}秒')
复制代码
常见问题和解决方案
问题1:匹配多行文本
默认情况下,.不匹配换行符。要匹配多行文本,可以使用re.DOTALL标志。
- import re
- html = '''
- <div class="content">
- <p>第一段</p>
- <p>第二段</p>
- </div>
- '''
- # 不使用re.DOTALL
- pattern1 = r'<div class="content">(.*?)</div>'
- match1 = re.search(pattern1, html)
- print('不使用re.DOTALL:', match1.group(1) if match1 else '无匹配')
- # 使用re.DOTALL
- pattern2 = r'<div class="content">(.*?)</div>'
- match2 = re.search(pattern2, html, re.DOTALL)
- print('使用re.DOTALL:', match2.group(1) if match2 else '无匹配')
复制代码
问题2:处理Unicode字符
要正确处理Unicode字符,可以使用re.UNICODE标志(在Python 3中是默认的)。
- import re
- text = 'English: hello, 中文: 你好, 日本語: こんにちは'
- # 匹配所有单词(包括非英文字符)
- words = re.findall(r'\w+', text, re.UNICODE)
- print('所有单词:', words)
- # 匹配所有中文字符
- chinese_chars = re.findall(r'[\u4e00-\u9fff]+', text)
- print('中文字符:', chinese_chars)
复制代码
问题3:提取嵌套标签内容
正则表达式不适合处理嵌套结构,但对于简单的嵌套,可以使用以下技巧:
- import re
- html = '''
- <div id="outer">
- <p>外层内容</p>
- <div id="inner">
- <p>内层内容</p>
- </div>
- <p>更多外层内容</p>
- </div>
- '''
- # 提取最外层div的内容(不包含内层div)
- outer_pattern = r'<div id="outer">(.*?)(?=<div id="inner">|$)'
- outer_match = re.search(outer_pattern, html, re.DOTALL)
- print('外层div内容(不包含内层div):', outer_match.group(1) if outer_match else '无匹配')
- # 提取内层div的内容
- inner_pattern = r'<div id="inner">(.*?)</div>'
- inner_match = re.search(inner_pattern, html, re.DOTALL)
- print('内层div内容:', inner_match.group(1) if inner_match else '无匹配')
复制代码
问题4:处理HTML实体
HTML中的特殊字符可能会以实体形式出现,如<表示<,>表示>等。提取文本时可能需要将这些实体转换回原始字符。
- import re
- import html
- text = 'HTML实体: <div> & " ''
- # 使用html模块解码HTML实体
- decoded_text = html.unescape(text)
- print('解码后的文本:', decoded_text)
- # 使用正则表达式替换特定HTML实体
- def replace_html_entities(text):
- # 替换常见的HTML实体
- text = re.sub(r'<', '<', text)
- text = re.sub(r'>', '>', text)
- text = re.sub(r'&', '&', text)
- text = re.sub(r'"', '"', text)
- text = re.sub(r''', "'", text)
- return text
- print('正则替换后的文本:', replace_html_entities(text))
复制代码
问题5:处理动态生成的ID或类名
有时网页中的ID或类名是动态生成的,包含随机字符串。可以使用正则表达式匹配这些动态部分。
- import re
- html = '''
- <div id="content-abc123">
- <p>内容</p>
- </div>
- <div class="item-def456 active">
- <p>项目</p>
- </div>
- '''
- # 匹配动态ID(id="content-"后跟任意字符)
- id_pattern = r'id="content-[^"]*"'
- id_match = re.search(id_pattern, html)
- print('匹配到的动态ID:', id_match.group(0) if id_match else '无匹配')
- # 匹配动态类名(class="item-"后跟任意字符,可能还有其他类)
- class_pattern = r'class="item-[^"]*\s*active"'
- class_match = re.search(class_pattern, html)
- print('匹配到的动态类名:', class_match.group(0) if class_match else '无匹配')
复制代码
总结
正则表达式是Python爬虫开发中不可或缺的工具,它能够帮助我们高效地从网页文本中提取所需数据。本文从正则表达式的基础语法开始,逐步深入到Python中的实际应用,并通过丰富的实战案例,展示了正则表达式在爬虫数据提取中的强大功能。
我们学习了正则表达式的基本元字符、字符类、分组和捕获等概念,掌握了Python中re模块的主要函数和方法,了解了如何从HTML中提取数据,处理各种网页结构。通过实战案例,我们看到了正则表达式在实际爬虫项目中的应用,包括提取图片链接、新闻文章、商品信息和社交媒体数据等。
此外,我们还探讨了一些高级技巧,如贪婪与非贪婪匹配、前瞻和后顾、使用回调函数处理匹配、使用命名组等,以及一些最佳实践和性能优化建议,帮助读者写出更高效、更健壮的正则表达式。
虽然正则表达式非常强大,但它并不是万能的。对于复杂的HTML解析,使用专门的解析库如BeautifulSoup或lxml可能更加合适。然而,对于许多简单的数据提取任务,正则表达式仍然是最快捷、最灵活的解决方案。
通过不断练习和应用,相信读者能够熟练掌握正则表达式这一强大的工具,在Python爬虫开发中游刃有余,高效地提取和处理网络数据。
版权声明
1、转载或引用本网站内容(Python爬虫入门必学正则表达式让你高效抓取网页信息从基础语法到实战案例全面解析助你快速掌握数据提取技能)须注明原网址及作者(威震华夏关云长),并标明本网站网址(https://pixtech.cc/)。
2、对于不当转载或引用本网站内容而引起的民事纷争、行政处理或其他损失,本网站不承担责任。
3、对不遵守本声明或其他违法、恶意使用本网站内容者,本网站保留追究其法律责任的权利。
本文地址: https://pixtech.cc/thread-41614-1-1.html
|
|