爬虫基本工系列之--数据获取
Posted 一腔诗意醉了酒
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了爬虫基本工系列之--数据获取相关的知识,希望对你有一定的参考价值。
1. 字符串操作
1.1 字符串截取
str = "helloWorld"
# 1. 字符串截取
# 格式 字符串 [开始位置: 结束位置 :间隔位置 ]
# 间隔位置 正数从左边开始,负数从右边开始
print( str[0:3] ) # hel
print( str[1:3] ) # el
print( str[1:7:2] ) # elw
print( str[::-1] ) # dlroWolleh
print( str[-1:-3: -2]) # d
print( str[-3:]) # rld
print( str[:-5:-3]) # do
# --------------------------
1.2 字符串查找
# 2.查找
# 格式 字符串.find( '要查找的内容[,开始位置,结束位置]' )
# 开始位置和结束位置表示要查找的范围,若为空,则表示查找所有。
# 找到目标后,返回目标第一位内容所在的下标,没找到返回 -1
str = "helloWorld"
a = str.find('l')
print(a) # 2
print(str.find('o',5)) # 6
print(str.find('e',2,3)) # -1
1.3 字符串替换
# 3. 替换
# 格式 字符串.replace( '被替换内容','替换后的内容' )
# 需要注意的是,使用replace 替换后字符串仅为临时变化,需要使用变量接收才可以保存
# 值得注意的是,所有匹配的项都会被替换
str = "hello world"
a = str.replace('h','g')
print(a,str) # gelloWorld helloWorld
b = str.replace('o','gg')
print(b, str) # hellggWggrld helloWorld
print(str.replace('o','')) # hellWrld
# ---------
1.4 字符串分割
# 4. 分割
# 格式 str.split( '分割符', 分割次数 )
# 如果分割次数为空,则全部分割
# 返回列表
# 注意分割符不能空(可以是空格)
str = "hello world"
a = str.split('e')
print(a) # ['h', 'llo world']
print( str.split(' ')) # ['hello', 'world']
2. 正则表达式
正则表达式是用于处理字符串的强大工具,拥有自己独特的语法一独立的处理引擎,效率上可能不如字符串处理方法,但是功能十分强大。且大部分语法都是通用的,与编程语言无关,不被支持的也是不常用的部分。
2.1 正则语法
2.1.1 常用元字符
^
匹配开始位置$
匹配结尾位置.
匹配换行符(\\r\\n
)之外的任意字符,简称通配符?
匹配前面子表达式零次或一
次 —>懒惰模式*
匹配前面子表达式零次或多次 —>贪婪模式+
匹配前面子表达式一次或多次 —>占有模式\\
转义符{ n, m }
匹配闭区间[ n -> m ]
次[ ]
一个字符的集合,比如[1-9]
将匹配1到9中的任意一个字符( ?#注释 )
添加注释,括号内的内容不作为匹配内容
2.1.2 常用的转义字符(小写)
\\b
匹配一个单词边界(开头或结尾),也就是指单词和空格间的位置(开始和结束
)。例如, ‘er\\b’ 可以匹配"never" 中的 ‘er’,但不能匹配 “verb” 中的 ‘er’。\\d
匹配任意十进制数字(0-9)\\s
任何空白符\\w
字母,数字,下划线
2.1.3 常用的几种反义字符(大写)
\\B
匹配不是单词开头或结束的位置\\D
非数字\\S
非( 任何空白符 )\\W
非(字母,数字,下划线 )
2.1.4 懒惰限定符(?
的扩展)
-
“
*?
” 重复任意次,但尽可能少重复如 “acbacb” 正则 “a.*?b” 只会取到第一个"acb" 原本可以全部取到但加了限定符后,只会匹配尽可能少的字符 ,而"acbacb"最少字符的结果就是"acb"
-
“
+?
” 重复1次或更多次,但尽可能少重复与上面一样,只是至少要重复1次
-
“
??
” 重复0次或1次,但尽可能少重复如 “aaacb” 正则 “a.??b” 只会取到最后的三个字符"acb"
-
“
{ n,m }?
” 重复n到m次,但尽可能少重复如 “aaaaaaaa” 正则 “a{0,m}” 因为最少是0次所以取到结果为空
-
“
{ n, }?
” 重复n次以上,但尽可能少重复如 “aaaaaaa” 正则 “a{1,}” 最少是1次所以取到结果为 “a”
2.1.5 分组与捕获
分组与捕获详情看博客园
-
分组
分组是用圆括号“()”括起来的正则表达式,匹配出的内容就表示一个分组。分组有一个例外的情况,分组也可以不使用圆括号,而是使用 | 元字符来表示分组,| 的两侧是两个分组,例如,exp1 | exp2
表示两个分组,在严格意义闪给,不认为由|
构成的正则表达式是分组。分组和捕获在正则表达式中有着密切的联系,一般情况下,分组即捕获,都用小括号完成:
(exp)
:分组,并捕获该分组匹配到的文 本
( ? : exp )
:分组,但不捕获该分组匹配到的文本
- 捕获
什么是捕获呢?使用小括号指定一个子表达式后,子表达式匹配的文本(即匹配的内容)可以在其他子表达式中重复使用。
2.1.5.1 定义分组
定义分组的三种形式:
( exp )
:把括号内的正则作为一个分组,系统自动分配组号,可以通过分组号引用该分组;
(? P <name> exp)
:定义一个命名分组,分组的正则是exp,系统为该分组分配分组号,可以通过分组名或分组号引用该分组;
( ?: exp)
:定义一个不捕获分组,该分组只在当前位置匹配文本,在该分组之后,无法引用该分组,因为该分组没有分组名,没有分组号,也不会占用分组编号;
1,分组编号
在正则表达式中,分组编号是自动进行的。当使用圆括号表示分组时,从正则表达式的左边开始看,看到的第一个左括号 “(” 表示第一个分组,第二个 “(” 表示第二个分组,依次类推,需要注意的是,有一个隐含的全局分组(分组编号是0),就是整个正则表达式。默认情况下,正则表达式为每个分组自动分配一个组号,规则是:组号从1开始,从左向右,组号依次加1(base+1),例如,第一个分组的组号为1,第二个分组的组号为2,以此类推。
2,分组命名
分组不仅有编号,还能为分组设置名称,在Python中,使用(?Pexp)为正则表达式exp设置别名。
3,无捕获分组
无捕获分组没有名称,也没有编号,因此,无法引用无捕获分组,无捕获分组不会占用分组编号。
2.2 正则处理函数
2.2.1 re.match
函数
re.match( pattern, string, flags = 0)
尝试从字符串的开头开始匹配一个模式,如果成功,就返回一个匹配成功的对象,否则返回none
‘’’
pattern : 匹配的正则表达式
string : 要匹配的字符串
flags :标志位,用于控制正则表达式,如是否区分大小写,是否多行匹配等
flags的可选值:
re.l : 忽略大小写
re.M : 多行模式,改变^ 和 $ 的行为
import re
text = "This is the last one "
res = re.match( '(.*) is (.*?) .*', text, re.M | re.I )
if res:
print(res) # <re.Match object; span=(0, 21), match='This is the last one '>
print(len(text)) # 21
print('res.group()-->',res.group())
print('res.group(0)-->',res.group(0))
print('res.group(1)-->',res.group(1))
print('res.group(2)-->',res.group(2))
print('res.groups()-->',res.groups())
""" 结果
res.group()--> This is the last one
res.group(0)--> This is the last one
res.group(1)--> This
res.group(2)--> the
res.groups()--> ('This', 'the')
"""
else:
print("NO MATCH!!")
测试( .*? )
# 2(.*) 学习 ``
import re
# 2(.*) 学习
import re
text = "This is the last one "
res1 = re.match( '(.*) is (.*) (.*)', text, re.M | re.I ) # ('This', 'the last one', '') 1
res2 = re.match( '(.*) is (.*) (.*?)', text, re.M | re.I ) # ('This', 'the last one', '') 2
res3 = re.match( '(.*) is (.*?) (.*)', text, re.M | re.I ) # ('This', 'the', 'last one ') 3
res4 = re.match( '(.*?) is (.*) (.*)', text, re.M | re.I ) # ('This', 'the last one', '') 4
res5 = re.match( '(.*?) is (.*?) (.*?)', text, re.M | re.I ) # ('This', 'the', '') 5
res6 = re.match( '(.*?)(.*?)(.*?)', text, re.M | re.I ) # ('', '', '') 6
print(res1.groups())
- 匹配结果解释
因为re.match
是尝试从字符串开头开始匹配所写的模式串,匹配成功则返回
所以匹配的步骤是:
- 匹配主串与模式串的长度,如果主串的长度比模式串的长度(指的是正则生效是所需要的最小子串长度)要小,则直接返回none
- 长度没问题才开始匹配
结果解释
1.res1
的结果注意is
前后的空格匹配,匹配完了This is
得到第一个分组This
,因为第二个分组优先贪婪于第三个分组,所以,第二个分组的结果是the last one
,第三个分组可以匹配任意字符串的字符,所以可以是空串
。注意要整体匹配,就是说在匹配前面的字符串也要兼顾后面能不能匹配。
2. res2
跟res1
差不多
3. res3
要注意第二个分组的懒惰模式,所以第二个分组匹配到the
4. res4,res5
差不多
5. res6
要注意都是懒惰模式,大家都不想匹配所以得到了三个空的分组。
2.2.2 re.search
函数
res.search( pattern, string , flags = 0)
用法跟re.match()
类似,但两者的匹配逻辑不一样
re.match
是从字符串开头开始匹配,如果开始不符合,就匹配失败re.search
是匹配整个字符串,直到找到第一个匹配的模式串,否则才返回none.
coding
import re
text = 'abbacb'
res1 = re.match('(b)(.*?)(.*)', text, re.M | re.I)
try:
print(res1.groups())
except:
print('匹配失败',res1)
res2 = re.search('(b)(.*?)(.*)', text, re.M | re.I)
try:
print(res2.groups())
except:
print('匹配失败',res2)
'''结果
匹配失败 None
('b', '', 'bacb')
'''
- 结果分析
- 可以看到
re,match
因为开头的a
不匹配,所以返回了none
,注意注意如果不做异常处理的话,会出现问题,因为none
没有属性方法 re.search()
匹配成功。
2.2.3 re.findall
- 用法跟
re.search
一致,只是search
匹配的是一个,成功之后返回一个对象,但是findall
是匹配所有成功的子串,并返回一个列表
2.2.4 re.sub
函数
re.sub ( pattern, repl, string, count=0, flags = 0 )
re.sub
函数用于替换字符串中的匹配项,如果没有匹配的项,则返回没有匹配的字符串
# pattern ->匹配的正则表达式
# repl ->用于替换的字符串
# string ->要被替换的字符串
# count ->替换的次数
# flags -> 标志位,用于控制正则表达式的匹配方式,跟match的一致
coding
import re
# 将后四位的数字替换成0
replace_value = re.sub('\\d{4}$', '0000', '123451234')
print(replace_value) # 123450000
2.2.5 re.split
函数
re.split( pattern, string, [,maxsplit])
用来匹配 pattern
的子串来分割字符串,返回一个列表,注意分隔符不进入结果列表
import re
res = re.split('a', 'abacbdbcaacd')
print(res)
res = re.split('a', 'bbabacbdbcaacd')
print(res)
res = re.split('a', 'bbabacbdbcaacd', 2)
print(res)
'''
['', 'b', 'cbdbc', '', 'cd']
['bb', 'b', 'cbdbc', '', 'cd']
['bb', 'b', 'cbdbcaacd']
'''
2.2.99 附re.pyi
的部分源码
if sys.version_info >= (3, 6):
import enum
class RegexFlag(enum.IntFlag):
A: int
ASCII: int
DEBUG: int
I: int
IGNORECASE: int
L: int
LOCALE: int
M: int
MULTILINE: int
S: int
DOTALL: int
X: int
VERBOSE: int
U: int
UNICODE: int
T: int
TEMPLATE: int
A = RegexFlag.A
ASCII = RegexFlag.ASCII
DEBUG = RegexFlag.DEBUG
I = RegexFlag.I
IGNORECASE = RegexFlag.IGNORECASE
L = RegexFlag.L
LOCALE = RegexFlag.LOCALE
M = RegexFlag.M
MULTILINE = RegexFlag.MULTILINE
S = RegexFlag.S
DOTALL = RegexFlag.DOTALL
X = RegexFlag.X
VERBOSE = RegexFlag.VERBOSE
U = RegexFlag.U
UNICODE = RegexFlag.UNICODE
T = RegexFlag.T
TEMPLATE = RegexFlag.TEMPLATE
_FlagsType = Union[int, RegexFlag]
else:
A: int
ASCII: int
DEBUG: int
I: int
IGNORECASE: int
L: int
LOCALE: int
M: int
MULTILINE: int
S: int
DOTALL: int
X: int
VERBOSE: int
U: int
UNICODE: int
T: int
TEMPLATE: int
_FlagsType = int
3. BeautifulSoup
数据清洗
3.1 先来看一个小例子
-
- 首先新建一个
test.html
- 首先新建一个
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>你好Beautiful</title>
</head>
<body>
<p id="python">
<a href='/index.html'>python</a>
BeautifulSoup的使用
</p>
<p class="myClass">
<a href='https://www.baidu.com'>这个一个指向百度的URL</a>
</p>
</body>
</html>
-
- 使用
BeautifulSoup + html5lib
- 使用
# 导入BeautifulSoup
from bs4 import BeautifulSoup
# 读取html文件
f = open('./test.html','r',encoding='utf-8')
html = f.read()
f.close()
# 记得先安装包 -> python -m pip install html5lib
soup = BeautifulSoup(html, 'html5lib')
print('html_title=', soup.title.getText())
print('----------------')
p = soup.find('p', id='python')
print('id=python-->\\n', p)
print('================')
all_p = soup.find_all('p')
for i, k in enumerate(all_p):
print('the ' + str(i+1) + ' p is ' + k.getText())
- 结果
- 通过例子可以发现
beautifulSoup
的使用极其简单,后面我们再深入学习一下下。
3.2 详细用法 (依赖前面给的test.html
)
3.2.1 获取标签
- 获取单个标签(返回符合条件的第一个标签)
直接通过
beautifulSoup对象.
来获取()
- 获取同一类型的所有标签
beautifulSoup.find_all('tag')
-> tag为要获取的标签名字
- coding
# 导入BeautifulSoup
from bs4 import BeautifulSoup
# 读取html文件
f = open('./test.html','r',encoding='utf-8')
html = f.read()
f.close()
# 记得现安装包 -> python -m pip install html5lib
soup = BeautifulSoup(html, 'html5lib')
# 获取标签
# print(soup.title)
''' <title>你好Beautiful</title> '''
# 获取标签里的文本
# print('html_title=', soup.title.getText())
""" html_title= 你好Beautiful """
# 返回第一个符合条件的标签
# print(soup.body.p)
""" <p id="python">
<a href="/index.html">python</a>
BeautifulSoup的使用
</p>
"""
# print(type(soup.body.p))
# <class 'bs4.element.Tag'>
# print(type( str (soup.body.p)))
# <class 'str'>
# 2. 获取所有标签
# all_p = soup.find_all('p')
# print(all_p)
"""
[
<p id="python">
<a href="/index.html">python</a>
BeautifulSoup的使用
</p>, <p class="myClass">
<a href="https://www.baidu.com">这个一个指向百度的URL</a>
</p>
]
"""
# for i, k in enumerate(all_p):
# print('the ' + str(i+1) + ' p is ' + k.getText())
"""
the 1 p is
python
BeautifulSoup的使用
the 2 p is
这个一个指向百度的URL
"""
3.2.2 获取标签属性
# 导入BeautifulSoup
from bs4 import BeautifulSoup
# 读取html文件
f = open('./test.html','r',encoding='utf-8')
html = f.read()
f.close()
# 记得现安装包 -> python -m pip install html5lib
soup = BeautifulSoup(html, 'html5lib')
# 2. 获取标签属性
print(soup.p['id'])
# python
print(soup.a.href, '\\n====\\n',soup.a['href'])
'''结果 ->注意要使用中括号加字符串的形式
None
====
/index.html
'''
3.2.3 精准查找
获取
id='xx'的标签
beautifulSoup对象.find_all ( 'tag', id='xx' )
beautifulSoup对象.find_all ( 'tag', class='xx' )
- test.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>你好Beautiful</title>
</head>
<body>
<p id="python" class="myClass">
<a href='/index.html'>python</a>
BeautifulSoup的使用
</p>
<p class="myClass" id='hello'>
<a href='https://www.baidu.com'>这个一个指向百度的URL</a以上是关于爬虫基本工系列之--数据获取的主要内容,如果未能解决你的问题,请参考以下文章