Python网络爬虫进阶+正则表达式

本文介绍了HTML的基础知识,包括结构、标签和样式,接着深入探讨了正则表达式的元字符和re模块的方法。通过实例展示了如何使用正则进行匹配,并且讲解了BeautifulSoup库创建对象、四大对象种类及其常用方法,最后给出了两个网络爬虫案例,分别涉及职位信息和电影数据的抓取与处理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1 HTML基础

1.1 HTML结构

HTML是超文本标记语言,被web浏览器进行解析查看。它的文档制作简单,功能强大 ,支持很多其他格式文件的嵌入。”超文本“文档主要由头部(head)和主体(body )两部分组成

<head> 定义了文档的信息
<title> 定义了文档的标题
<base> 定义了页面链接标签的默认链接地址
<link> 定义了一个文档和外部资源之间的关系
<meta> 定义了HTML文档中的元数据
<script> 定义了客户端的脚本文件
<style> 定义了HTML文档的样式文件

1.2 HTML各标签结构

  1. div是标签名
  2. id是为该标签设置的唯一标识
  3. class是该标签的样式类
  4. attr是设置的其他属性
  5. content是的标签的文本内容

1.3 HTML样式

  1. id选择器:#id_name,选择id=id_name的所有元素
  2. 类选择器:.cls_name,选择class=cls_name的所有元素
  3. 标签选择:lable,选择标签名为lable的所有元素,用逗号隔开可选择多个标签
  4. 全部选择:*,选择所有的元素
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>网页标题</title>
    <style>
        *{  /*通配符*/
            font-size:50px;
        }
        h1{  /*标签选择器*/
            color:red;
        }
        div.content{ /*类选择器,不唯一,可以多个标签使用同一个class名*/
            background-color:blue;
        }
        #name{  /*id选择器*/
            border: 4px solid red;
        }
        a span{ /*选中a标签内的所有span标签*/
            color:maroon;
        }
        a>span{ /*只选中a标签的第一级子标签中的span*/

        }
        a[title="新浪"]{

        }
    </style>
</head>
<body>
    <h1>演示HTML文件</h1>
    <span>这是一个span标签</span>
    <a href="http://www.baidu.com">
        <div>
             <span>跳转到百度网</span>
        </div>

    </a>
    <h1>第二个标题</h1>
    <div class="content">这是网页内容</div>
    <h3 id="name">用户名:张三</h3>
    <p class="content">这是一篇文章</p>
    <a href="http://www.sina.com" title="新浪">新浪网</a>

</body>
</html>

运行结果:
这里写图片描述

2.正则表达式

正则表达式(或 RE)是一种小型的、高度专业化的编程语言,(在Python中)它内嵌在Python中,并通过 re 模块实现。正则表达式模式被编译成一系列的字节码,然后由用 C 编写的匹配引擎执行。

字符匹配(普通字符,元字符):

1 普通字符:大多数字符和字母都会和自身匹配

re.findall('alvin','yuanaleSxalexwupeiqi')
#['alvin'] 

2 元字符:. ^ $ * + ? { } [ ] | ( ) \

2.1 元字符

2.1.1 元字符之. ^ $ * + ? { }

import re

ret=re.findall('a..in','helloalvin')
print(ret)#['alvin']


ret=re.findall('^a...n','alvinhelloawwwn')
print(ret)#['alvin']


ret=re.findall('a...n$','alvinhelloawwwn')
print(ret)#['awwwn']


ret=re.findall('a...n$','alvinhelloawwwn')
print(ret)#['awwwn']


ret=re.findall('abc*','abcccc')#贪婪匹配[0,+oo]  
print(ret)#['abcccc']

ret=re.findall('abc+','abccc')#[1,+oo]
print(ret)#['abccc']

ret=re.findall('abc?','abccc')#[0,1]
print(ret)#['abc']


ret=re.findall('abc{1,4}','abccc')
print(ret)#['abccc'] 贪婪匹配
#前面的*,+,?等都是贪婪匹配,也就是尽可能匹配,后面加?号使其变成惰性匹配
ret=re.findall('abc*?','abcccccc')
print(ret)#['ab']

2.1.2 元字符之字符集[]

ret=re.findall('a[bc]d','acd')
print(ret)#['acd']

ret=re.findall('[a-z]','acd')
print(ret)#['a', 'c', 'd']

ret=re.findall('[.*+]','a.cd+')
print(ret)#['.', '+']

#在字符集里有功能的符号: - ^ \

ret=re.findall('[1-9]','45dha3')
print(ret)#['4', '5', '3']

ret=re.findall('[^ab]','45bdha3')
print(ret)#['4', '5', 'd', 'h', '3']

ret=re.findall('[\d]','45bdha3')
print(ret)#['4', '5', '3']

2.1.3 元字符之转义符 \

反斜杠后边跟元字符去除特殊功能,比如.
反斜杠后边跟普通字符实现特殊功能,比如\d

  • \d 匹配任何十进制数;它相当于类 [0-9]。
  • \D 匹配任何非数字字符;它相当于类 [^0-9]。
  • \s 匹配任何空白字符;它相当于类 [ \t\n\r\f\v]。
  • \S 匹配任何非空白字符;它相当于类 [^ \t\n\r\f\v]。
  • \w 匹配任何字母数字字符;它相当于类 [a-zA-Z0-9_]。
  • \W 匹配任何非字母数字字符;它相当于类 [^a-zA-Z0-9_]
  • \b 匹配一个特殊字符边界,比如空格 ,&,#等
ret=re.findall('I\b','I am LIST')
print(ret)#[]
ret=re.findall(r'I\b','I am LIST')
print(ret)#['I']

#-----------------------------eg1:
import re
ret=re.findall('c\l','abc\le')
print(ret)#[]
ret=re.findall('c\\l','abc\le')
print(ret)#[]
ret=re.findall('c\\\\l','abc\le')
print(ret)#['c\\l']
ret=re.findall(r'c\\l','abc\le')
print(ret)#['c\\l']

#-----------------------------eg2:
#之所以选择\b是因为\b在ASCII表中是有意义的
m = re.findall('\bblow', 'blow')
print(m)
m = re.findall(r'\bblow', 'blow')
print(m)

2.1.4 元字符之分组()

m = re.findall(r'(ad)+', 'add')
print(m)

ret=re.search('(?P<id>\d{2})/(?P<name>\w{3})','23/com')
print(ret.group())#23/com
print(ret.group('id'))#23

2.1.4 元字符之|

ret=re.search('(ab)|\d','rabhdg8sd')
print(ret.group())#ab

2.1.5 正则表达式模式总结

模式描述
^匹配字符串的开头
$匹配字符串的末尾。
.匹配任意字符,除了换行符,当re.DOTALL标记被指定时,则可以匹配包括换行符的任意字符。
[…]用来表示一组字符,单独列出:[amk] 匹配 ‘a’,’m’或’k’
[^…]不在[]中的字符:[^abc] 匹配除了a,b,c之外的字符。
re*匹配0个或多个的表达式。
re+匹配1个或多个的表达式。
re?匹配0个或1个由前面的正则表达式定义的片段,非贪婪方式
re{ n}匹配n个前面表达式。例如,”o{2}”不能匹配”Bob”中的”o”,但是能匹配”food”中的两个o。
re{ n,}精确匹配n个前面表达式。例如,”o{2,}”不能匹配”Bob”中的”o”,但能匹配”foooood”中的所有o。”o{1,}”等价于”o+”。”o{0,}”则等价于”o*”。
re{ n, m}匹配 n 到 m 次由前面的正则表达式定义的片段,贪婪方式
aIb匹配a或b
(re)G匹配括号内的表达式,也表示一个组
(?imx)正则表达式包含三种可选标志:i, m, 或 x 。只影响括号中的区域。
(?-imx)正则表达式关闭 i, m, 或 x 可选标志。只影响括号中的区域。
(?: re)类似 (…), 但是不表示一个组
(?imx: re)在括号中使用i, m, 或 x 可选标志
(?-imx: re)在括号中不使用i, m, 或 x 可选标志
(?#…)注释.
(?= re)前向肯定界定符。如果所含正则表达式,以 … 表示,在当前位置成功匹配时成功,否则失败。但一旦所含表达式已经尝试,匹配引擎根本没有提高;模式的剩余部分还要尝试界定符的右边。
(?! re)前向否定界定符。与肯定界定符相反;当所含表达式不能在字符串当前位置匹配时成功。
(?> re)匹配的独立模式,省去回溯。
\w匹配数字字母下划线
\W匹配非数字字母下划线
\s匹配任意空白字符,等价于 [\t\n\r\f]。
\S匹配任意非空字符
\d匹配任意数字,等价于 [0-9]。
\D匹配任意非数字
\A匹配字符串开始
\Z匹配字符串结束,如果是存在换行,只匹配到换行前的结束字符串。
\z匹配字符串结束
\G匹配最后匹配完成的位置。
\b匹配一个单词边界,也就是指单词和空格间的位置。例如, ‘er\b’ 可以匹配”never” 中的 ‘er’,但不能匹配 “verb” 中的 ‘er’。
\B 匹配非单词边界。‘er\B’ 能匹配 “verb” 中的 ‘er’,但不能匹配 “never” 中的 ‘er’。
\n, \t, 等。匹配一个换行符。匹配一个制表符, 等
\1…\9匹配第n个分组的内容。
\10匹配第n个分组的内容,如果它经匹配。否则指的是八进制字符码的表达式。

2.2 re模块下的常用方法

import re
#1
re.findall('a','alvin yuan')    #返回所有满足匹配条件的结果,放在列表里
#2
re.search('a','alvin yuan').group()  #函数会在字符串内查找模式匹配,只到找到第一个匹配然后返回一个包含匹配信息的对象,该对象可以
                                     # 通过调用group()方法得到匹配的字符串,如果字符串没有匹配,则返回None。

#3
re.match('a','abc').group()     #同search,不过尽在字符串开始处进行匹配

#4
ret=re.split('[ab]','abcd')     #先按'a'分割得到''和'bcd',在对''和'bcd'分别按'b'分割
print(ret)#['', '', 'cd']

#5
ret=re.sub('\d','abc','alvin5yuan6',1)
print(ret)#alvinabcyuan6
ret=re.subn('\d','abc','alvin5yuan6')
print(ret)#('alvinabcyuanabc', 2)

#6
obj=re.compile('\d{3}')
ret=obj.search('abc123eeee')
print(ret.group())#123
ort re
ret=re.finditer('\d','ds3sy4784a')
print(ret)        #<callable_iterator object at 0x10195f940>

print(next(ret).group())
print(next(ret).group())

#注意:

import re

ret=re.findall('www.(baidu|oldboy).com','www.oldboy.com')
print(ret)#['oldboy']     这是因为findall会优先把匹配结果组里内容返回,如果想要匹配结果,取消权限即可

ret=re.findall('www.(?:baidu|oldboy).com','www.oldboy.com')
print(ret)#['www.oldboy.com']

爬虫案例 1

爬取51job爬虫所爬取的职位信息数据,并将数据保存进excel中

import re
import requests
import xlwt

url = "https://search.51job.com/list/020000,000000,0000,00,9,99,python,2,{}.html?lang=c&stype=1&postchannel=0000&workyear=99&cotype=99&degreefrom=99&jobterm=99&companysize=99&lonlat=0%2C0&radius=-1&ord_field=0&confirmdate=9&fromType=&dibiaoid=0&address=&line=&specialarea=00&from=&welfare="

# 爬取第一页数据
firstpage = url.format(1)
response = requests.get(firstpage)

# 转化成gbk编码
html = str(response.content, "gbk")

# print(html)

# 获取总页数
pattern = re.compile('<span class="td">共(.*?)页,到第</span>', re.S)

result = re.findall(pattern, html)

totalPage = int(result[0])
# print("总页数:{}".format(totalPage))

# 循环爬取所有数据
for page in range(1, 11):#这里仅爬取十页信息
    # print("---------------正在爬取第{}页数据---------------".format(page))
    # 组装当前页的页码
    currentUrl = url.format(page)
    # print(currentUrl[0:80])
    response = requests.get(currentUrl)
    html = str(response.content, "gbk")
    reg = re.compile('class="t1 ">.*? <a target="_blank" title="(.*?)".*? <span class="t2"><a target="_blank" title="(.*?)".*?<span class="t3">(.*?)</span>.*?<span class="t4">(.*?)</span>.*? <span class="t5">(.*?)</span>',re.S)  # 匹配换行符
    #获取一页中所有的职位信息
    onePage = re.findall(reg,html)
    #循环输出一页中所有的职位信息
    # for jobName, company, place, salary, postDate in onePage:
    #     pass
    #     print("名称:{},公司:{},地址:{},薪资:{},发布日期:{}".format(jobName, company, place, salary, postDate))
    #     print("******************************************************")
def excel_write(items,index):
    for item in items:#职位信息
        for i in range(0,5):
            #print item[i]
            ws.write(index,i,item[i])#行,列,数据
        # print(index)
        index += 1
newTable="51job.xls"#表格名称
wb = xlwt.Workbook(encoding='utf-8')#创建excel文件,声明编码
ws = wb.add_sheet('sheet1')#创建表格
headData = ['招聘职位','公司','地址','薪资','日期']#表头信息
for colnum in range(0, 5):
    ws.write(0, colnum, headData[colnum], xlwt.easyxf('font: bold on'))  # 行,列

for each in range(1,10):
    index=(each-1)*50+1
    excel_write(onePage,index)

wb.save(newTable)

运行结果:
这里写图片描述

3 Beautiful Soup

3.1 创建 Beautiful Soup 对象并打印对象内容

import requests
from bs4 import BeautifulSoup as bs

html = requests.get("https://www.baidu.com").text

# print(html
soup = bs(html,"lxml")  #html.parser/lxml/html5lib

#将html字符串数据美化输出
# print(soup.prettify())
print(soup.name)

3.2 四大对象种类

Beautiful Soup将复杂HTML文档转换成一个复杂的树形结构,每个节点都是 Python对象,所有对象可以归纳为4种:
- Tag
Tag:就是 HTML 中的一个个标签。 语法形式:soup.标签名 用来查找的是在所有内容中的第一个符合要求的标签 Tag 有两个属性,是 name 和 attrs。 soup 对象的 name 是输出的值便为标签本身的名称。 soup 对象的attrs是把标签的所有属性放在一个字典内

  • NavigableString
    获取标签内部的文字
    soup.标签名.string
  • BeautifulSoup
  • Comment
    Comment 对象是一个特殊类型的 NavigableString 对象,其实输出的内容仍然不包 括注释符号

3.3常用方法

(1)find_all(name=None, attrs={}, recursive=True, text=None, limit=None, **kwargs) 搜索当前tag的所有tag子节点,并判断是否符合过滤器的条件
(2)find(name=None, attrs={}, recursive=True, text=None, **kwargs) 搜索当前tag的第一个tag子节点,并判断是否符合过滤器的条件
movieQuote = movieLi.find(‘span’, attrs={‘class’: ‘inq’})
(3)直接子节点:.contents .children属性
(4)所有子孙节点:.descendants属性
(5)获取文本:.string属性
(6)父节点:.parent .parents(迭代器)
(7)兄弟节点:.next_sibling .previous_sibling 加s同上
(8)下一个与上一个要解析的元素:.next_elements .previous_element


1)name 参数 name 参数可以查找所有名字为 name 的tag,字符串对象会被自动忽略掉
2)attrs 参数 注意:如果一个指定名字的参数不是搜索内置的参数名,搜索时会把该参数当作指定名字tag 的属性来搜索,如果包含一个名字为 id 的参数,Beautiful Soup会搜索每个tag的”id”属性
3)text 参数 通过 text 参数可以搜搜文档中的字符串内容.与 name 参数的可选值一样, text 参数接受 字 符串 , 正则表达式 , 列表, True
4)limit 参数 find_all() 方法返回全部的搜索结构,如果文档树很大那么搜索会很慢.如果我们不需要全部结 果,可以使用 limit 参数限制返回结果的数量.效果与SQL中的limit关键字类似,当搜索到的结果 数量达到 limit 的限制时,就停止搜索返回结果.
5)recursive 参数 调用tag的 find_all() 方法时,Beautiful Soup会检索当前tag的所有子孙节点,如果只想搜索 tag的直接子节点,可以使用参数 recursive=False .


代码实例

from bs4 import BeautifulSoup as bs

html = '''
<html><head><title>The Dormouse's story</title></head>
<body>
<p class="title"><b>The Dormouse's story</b></p>

<p class="story">Once upon a time there were three little sisters; and their names were
<a href="http://example.com/elsie" class="sister" id="link1">jack</a>,
<a href="http://example.com/lacie" class="sister" id="link2">Alice</a> and
<a href="http://example.com/tillie" class="sister" id="link3">Mary</a>;
and they lived at the bottom of a well.</p>

<p class="story">...</p>
'''

soup = bs(html, "html.parser")

# print(soup.prettify())

# 获取Tag标签的属性
# print(soup.a["id"])
# print(soup.a.get("id"))
# print(soup.a.get("class"))


# 获取标签内的文字
# print(soup.a.string)
# print(soup.b.string)

# soup.find()只匹配第一个符合规则的Tag标签
# secondLink = soup.find("a",attrs={"class":"sister"})
# print(secondLink.string)


#soup.find_all()匹配所有符合规则的Tag标签

# links = soup.find_all("a",attrs={"class":"sister"})
# print(links[1].get("id"))

p = soup.find("p")

#在p标签内查找b标签
# b = p.find("b")
# print(b)

#获取父标签
# xTag = p.parent
# print(xTag.name)

#获取所有的父标签
for parent in p.parents:
    print(parent.name)

爬虫案例 2

爬取豆瓣电影top250条电影数据,并存储进MySQL

import requests
from bs4 import BeautifulSoup as bs
import pymysql

movieInfos = []  # 用于保存所有的电影信息
baseUrl = 'https://movie.douban.com/top250?start={}&filter='
for startIndex in range(0, 226, 25):
    url = baseUrl.format(startIndex)
    # 爬取网页
    r = requests.get(url)
    # 获取html内容
    htmlContent = r.text
    # 用BeautifulSoup加载html文本内容进行处理
    soup = bs(htmlContent, "lxml")
    # 获取到页面中索引的class名为info的标签(应该有25个)
    movieList = soup.find_all("div", attrs={"class": "info"})
    # 遍历25条电影信息
    for movieItem in movieList:
        movieInfo = {}  # 创建空字典,保存电影信息
        # 获取到名为class名为hd的div标签内容
        hd_div = movieItem.find("div", attrs={"class": "hd"})
        # 通过bd_div获取到里面第一个span标签内容
        hd_infos = hd_div.find("span").get_text().strip().split("\n")
        # < span  class ="title" > 天堂电影院 < / span >
        movieInfo['title'] = hd_infos[0]

        # 获取到class名为bd的div标签内容
        bd_div = movieItem.find("div", attrs={"class": "bd"})
        # print(bd_div)
        # 通过bd_div获取到里面第一个p标签内容
        infos = bd_div.find("p").get_text().strip().split("\n")
        # print(infos)   #包含了两行电影信息的列表
        # 获取导演和主演
        infos_1 = infos[0].split("\xa0\xa0\xa0")
        if len(infos_1) == 2:
            # 获取导演,只获取排在第一位的导演名字
            director = infos_1[0][4:].rstrip("...").split("/")[0]
            movieInfo['director'] = director
            # 获取主演
            role = infos_1[1][4:].rstrip("...").rstrip("/").split("/")[0]
            movieInfo['role'] = role
        else:
            movieInfo['director'] = None
            movieInfo['role'] = None
        # 获取上映的时间/地区/电影类型
        infos_2 = infos[1].lstrip().split("\xa0/\xa0")
        # 获取上映时间
        year = infos_2[0]
        movieInfo['year'] = year
        # 获取电影地区
        area = infos_2[1]
        movieInfo['area'] = area
        # 获取类型
        genre = infos_2[2]
        movieInfo['genre'] = genre
        print(movieInfo)
        movieInfos.append(movieInfo)
conn = pymysql.connect(host='localhost', user='root', passwd='DevilKing27', db='douban',charset="utf8")
# 获取游标对象
cursor = conn.cursor()
# 查看结果
print('添加了{}条数据'.format(cursor.rowcount))
for movietiem in movieInfos:
    director = movietiem['director']
    role = movietiem['role']
    year = movietiem['year']
    area = movietiem['area']
    genre = movietiem['genre']
    title = movietiem['title']
    sql = 'INSERT INTO top250 values("%s","%s","%s","%s","%s","%s")' % (director, role, year, area, genre, title)
    # 执行sql
    cursor.execute(sql)
    # 提交
    conn.commit()
    print('添加了{}条数据'.format(cursor.rowcount))

结果:
这里写图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值