06-爬虫数据提取-正则表达式
什么是正则表达式:
通俗理解:按照一定的规则,从某个字符串中匹配出想要的数据。这个规则就是正则表达式
re模块是python自带的标准库,不需要安装,直接导入
import re
1.单字符匹配:
点(.):匹配任意的字符(除了'\n')
\d:匹配任意的数字
\D:匹配任意的非数字
\s:匹配的是空白字符(包括:\n,\t,\r和空格)
\S:非空白字符
\w:匹配的是a-z和A-Z以及数字和下划线
[]组合的方式,只要满足中括号中的某一项都算匹配成功
# 匹配单个字符match,只是相对于第一个位置 import re text = "abc" # 1.match匹配单个字符串"a"(match方法从text的第一个位置进行匹配,符合则返回,不符合返回None并不会往下一个位置进行匹配) result = re.match("a",text) # 2.group返回默认分组 print(result.group()) # 点(.):匹配任意的字符(除了'\n') text2 = "+bc" result2 = re.match(".",text2) print("匹配任意的字符:",result2.group()) # \d:匹配任意的数字 text3 = "1b3" result3 = re.match("\d",text3) print("匹配任意的数字:",result3.group()) # \D:匹配任意的非数字 text4 = "&2c" result4 = re.match("\D",text4) print("匹配任意的非数字:",result4.group()) # \s:匹配的是空白字符(包括:\n,\t,\r和空格) text5 = "\tab" result5 = re.match("\s",text5) print("匹配的是空白字符:------{}------".format(result5.group())) # \S:非空白字符 text6 = "abc" result6 = re.match("\S",text6) print("匹配非空白字符:",result6.group()) # \w:匹配的是a-z和A-Z以及数字和下划线 text7 = "ABC" result7 = re.match("\w",text7) print("匹配的是a-z和A-Z以及数字和下划线:",result7.group()) # \W:匹配的是和\w相反的 text8 = "#BC" result8 = re.match("\W",text8) print("匹配的是和\w相反的:",result8.group()) # []组合的方式,只要满足中括号中的某一项都算匹配成功 text9 = "1bc" result9 = re.match("[1bc]",text9) print("[]组合的方式:",result9.group()) result9_2 = re.match("[\s\d]",text9) print("[]组合的方式:",result9_2.group())
2.多字符匹配:
# 匹配多个字符 import re text = "abc" result = re.match("ab",text) print(result.group()) # *:匹配任意多个字符 text1 = "ab+c" result1 = re.match("\w*",text1) print("匹配任意多个字符:",result1.group()) # +:匹配1个或者多个字符 text2 = "abc" result2 = re.match("\w+",text2) print("匹配1个或者多个字符:",result2.group()) # ?:匹配前一个字符0个或者1个 text3 = "a1b2c+" result3 = re.match("\w?",text3) print("匹配前一个字符0个或者1个:",result3.group()) # {m}:匹配m个字符 text4 = "abc123" result4 = re.match("\w{5}",text4) print("匹配m个字符:",result4.group()) # {m,n}:匹配m-n之间的个数的字符(当满足最大条件,默认最大条件) text5 = "abc123" result5 = re.match("\w{2,4}",text5) print("匹配m个字符:",result5.group())
实例:使用正则表达式验证手机号码、邮箱、URL、身份证号
# 正则表达式案例 import re def text(): while True: print("----------欢迎开始正则表达式测试-----------") print("1.验证手机号码") print("2.验证邮箱") print("3.验证URL") print("4.验证身份证号") print("5.退出") num = input("请输入要选择的数字:") if num == "1": # 1.验证手机号码:手机号码的规则是以1开头,第二位可以是34587,后面九位就可以任意随意($在正则里面表达是以空白字符结尾) text = input("请输入要验证的手机号码:") result = re.match("1[34587]\d{9}$",text) example = "手机号{}验证正确...".format(result.group()) if result else "手机号验证错误..." print(example) if num == "2": # 2.验证邮箱:邮箱的规则是邮箱名称是用数字、英文字母、下划线组成,然后是@符号,后面就是域名 text = input("请输入要验证的邮箱:") result = re.match("\w+@[a-z0-9]+\.[a-z]+",text) example = "邮箱{}验证正确...".format(result.group()) if result else "邮箱验证错误..." print(example) if num == "3": # 3.验证URL:URL的规则前面是http或者https或者ftp,然后再加上一个冒号,再加上一个斜杠,再后面就是可以出现任意非空白字符了 text = input("请输入要验证的URL:") result = re.match("(http|https|ftp)://\S+",text) example = "URL{}验证正确...".format(result.group()) if result else "URL验证错误..." print(example) if num == "4": # 4.验证身份证:身份证的规则是,总共18位,前面17位都是数字,后面一位可以是数字,也可以是小写的x,也可以是大写的X text = input("请输入要验证的身份证号:") result = re.match("\d{17}[\dxX]",text) example = "身份证号{}验证正确...".format(result.group()) if result else "身份证号验证错误..." print(example) if num == "5": print("感谢您的使用,下次再见...") break if __name__ == "__main__": text()
3.正则表达式-开始、结束、贪婪和非贪婪
^:以...开头
$:以...结尾
|:匹配多个字符串或者表达式
贪婪
非贪婪
# 正则表达式-开始、结束、贪婪和非贪婪 import re # ^:以...开头: text = "hello world" result = re.search("^hello",text) print("^开头:",result.group()) result_2 = re.match("hello",text) print("match:",result_2.group()) # $:以...结尾: text = "hello world" result = re.search("world$",text) print("$结尾:",result.group()) text = "" result = re.search("^$",text) print("$结尾:",result.group()) # |:匹配多个字符串或者表达式: # 贪婪和非贪婪: text = "12345" result = re.search("\d+",text) print("贪婪:",result.group()) result = re.search("\d+?",text) print("非贪婪:",result.group()) # 案例1:提取html标签名称: # text = "<h1>这是标题</h1>" text = "<head>这是标题</head>" result = re.search("<.+?>",text) print(result.group()) # 案例2:验证一个字符是不是0-100之间的数字: # 01 text = "0" result = re.match("0$|[1-9]\d?$|100$",text) print(result.group()) text = "6" result = re.match("0$|[1-9]\d?$|100$",text) print(result.group()) text = "99" result = re.match("0$|[1-9]\d?$|100$",text) print(result.group())
4.转义字符和原生字符
python中的转义字符
正则表达式中的转义字符
原生字符串和正则表达式
import re # python中的转义字符(r原生字符串) text = "hello\tworld" print("转义前",text) text_2 = r"hello\tworld" print("转义后",text_2) # 正则表达式中的转义字符($在正则里面表达是以空白字符结尾) text = "apple price is $99,range price is $88" result = re.findall("$\d+",text) print("转义前",result) result_2 = re.findall("\$\d+",text) print("转义后",result_2) # 原生字符串和正则表达式 # 正则表达式字符串的解析规则: # 1.先把这个字符串放在python语言层面进行解析 # 2.把python语言层面解析的结果再放到正则表达式层面进行解析 text = "\cba c" # \\\\c => python语言层面\\c => 正则表达式层面\c result1 = re.match("\\\\c",text) print("原生字符串和正则表达式1",result1.group()) result_2 = re.match(r"\\c",text) print("原生字符串和正则表达式2",result_2.group())
5.正则表达式:分组
findall:查找所有满足条件的
sub:根据规则替换其它字符串
split:根据规则分割字符串
compile:编译正则表达式
# search import re # 分组 # group()/group(0)匹配整个分组 # group(1)匹配第一个分组 group(2)匹配第二个分组 text = "apple price is $99,range price is $88" result = re.search(".+\$\d+.+\$\d+",text) print(result.group()) result_1 = re.search(".+(\$\d+).+(\$\d+)",text) print("第一个分组:{0}\t第二个分组:{1}".format(result_1.group(1),result_1.group(2))) # findall:查找所有满足条件的 text = "apple price is $99,range price is $88" result = re.findall(r"\$\d+",text) print("findall:",result) # sub:根据规则替换其它字符串 # sub方法比replace方法强大的多,sub使用正则进行替换 text = "nihao zhongguo hello world" result = text.replace(" ","-") print("字符串replace替换:",result) result_2 = re.sub(r" ","-",text) print("re中的sub替换:",result_2) # sub测试 html = """ <div class="job-detail"> <p>1. 3年以上相关开发经验 ,全日制统招本科以上学历</p> <p>2. 精通一门或多门开发语言(Python,C,Java等),其中至少有一门有3年以上使用经验</p> <p>3. 熟练使用ES/mysql/mongodb/redis等数据库;</p> <p>4. 熟练使用django、tornado等web框架,具备独立开发 Python/Java 后端开发经验;</p> <p>5. 熟悉 Linux / Unix 操作系统 </p> <p>6. 熟悉 TCP/IP,http等网络协议</p> <p>福利:</p> <p>1、入职购买六险一金(一档医疗+公司全额购买商业险)+开门红+全额年终奖(1年13薪,一般会比一个月高)</p> <p>2、入职满一年有2次调薪调级机会</p> <p>3、项目稳定、团队稳定性高,团队氛围非常好(汇合员工占招行总员工比例接近50%);</p> <p>4、有机会转为招商银行内部员工;</p> <p>5、团队每月有自己的活动经费,法定节假日放假安排;</p> <p>6、办公环境优良,加班有加班费(全额工资为计算基数,加班不超过晚上10点,平日加班为时薪1.5倍,周末加班为日薪2倍,周末加班也可优先选择调休,管理人性化)。</p> </div> """ new_html = re.sub(r"<.+?>","",html) print("sub获取html内容:",new_html) # split:根据规则分割字符串(得到list列表) text = "nihao zhongguo,hello world" result_list = re.split(r" |,",text) print("split分割:",result_list) # compile:编译正则表达式(提高效率,直接把编译好的正则表达式发送给服务器) text = "apple price is 34.56" # ?匹配0次或1次 *匹配0次或无数次 r = re.compile(r"\d+\.?\d*") print("compile编译正则表达式:",r) result = re.search(r,text) print("打印:",result.group()) # 如果想要再正则表达式中加注释,需要在后面加re.VERBOSE r2 = re.compile(r""" \d+ # 整数部分 \.? # 小数点 \d* # 小数部分 """,re.VERBOSE) print("compile编译正则表达式:",r2) result_2 = re.search(r2,text) print("打印:",result_2.group())
实战-赶集网租房信息爬虫(利用requests)
# 赶集网爬虫实战 import requests from fake_useragent import UserAgent import re import time # url = "http://nc.ganji.com/zufang/pn2/" # 用列表保存总page_urls page_urls = [] user_agent = UserAgent() headers = { "User-Agent":user_agent.chrome, "cookie":"ganji_xuuid=2ee320a9-26d9-449d-fb1a-fe9dab1f0f52.1611720150767; ganji_uuid=6987060722968396958522; _gl_tracker=%7B%22ca_source%22%3A%22www.baidu.com%22%2C%22ca_name%22%3A%22-%22%2C%22ca_kw%22%3A%22-%22%2C%22ca_id%22%3A%22-%22%2C%22ca_s%22%3A%22seo_baidu%22%2C%22ca_n%22%3A%22-%22%2C%22ca_i%22%3A%22-%22%2C%22sid%22%3A54450163782%7D; GANJISESSID=ompcp0chpheb6gjnau2f68r433; citydomain=nc; __utmc=32156897; __utma=32156897.318273985.1611720168.1611819400.1611821517.3; __utmz=32156897.1611821517.3.3.utmcsr=nc.ganji.com|utmccn=(referral)|utmcmd=referral|utmcct=/; __utmt=1; ganji_login_act=1611821604762; __utmb=32156897.4.10.1611821517" } # 获取详情页面信息 def parse_page(page_urls,f): page = 1 for page_url in page_urls: count = 1 print("正在爬取赶集网58南昌租房信息第{}页内容:".format(page),page_url) resp = requests.get(page_url,headers=headers) text = resp.content.decode("utf-8") # 利用正则表达式,获取a标签下的标题(re.DOTALL指的是"."可以代表所有字符,包括换行) houses = re.findall(r""" <div.+?ershoufang-list".+?<a.+?js-title.+?>(.+?)</a> # 获取房源的标题 .+?<dd.+?dd-item.+?<span>(.+?)</span> # 获取房源的户型 .+?<span.+?<span>(.+?)</span> # 获取房源的面积 .+?<span.+?<span>(.+?)</span> # 获取房源的朝向 .+?<div.+?price.+?<span.+?num">(.+?)</span> # 获取房源的租金 """,text,re.VERBOSE|re.DOTALL) for house in houses: print("正在获取第{0}页第{1}条的内容:{2}".format(page,count,house)) # 保存数据内容 house = ",".join(house)+"\n" f.write(house) count += 1 # time.sleep()推迟调用线程的运行 time.sleep(1) page += 1 def main(): # 保存文件到"F:/爬虫/第三章-xpath库/快代理.csv" (newline=""消除多余空行,添加ignore忽略报错) with open(r"F:/爬虫/第三章-xpath库/赶集网南昌租房信息.csv","a",encoding="gbk",newline="") as f: # 准备需要爬取页面的url page = int(input("请输入要爬取赶集网58南昌租房信息前几页内容:")) for i in range(1,page+1): page_url = "http://nc.ganji.com/zufang/pn{}/".format(i) page_urls.append(page_url) # 保存数据标题信息 title = "标题,户型,面积,朝向,租金\n" f.write(title) # 调用,获取详情页面 parse_page(page_urls,f) if __name__ == "__main__": main()
总结:
1.如果想要让"."代表所有的字符,那么就需要在函数后面加re.DOTALL来表示,否则不会代表\n,也就是换行
2.获取数据的时候,都要用非贪婪模式.+?
3.如果正则写的不对,那么获取不到结果,程序会假死,这时候可以把你刚刚写的正则删掉,重新运行下,看程序还会不会假死,如果不会假死,说明正则写的有问题,不要去钻牛角尖,换一个思路