introduction
学习python爬虫的笔记
basics
Web-basics
url
scheme://[username:password@]hostname[:port][/path][;parameters][?query][#fragment]
- scheme:协议,eg:http;https;ftp 也称作protocol
- username,password:用户,密码 有的url需要提供用户名和密码才能访问
- hostname:IP
- port:端口
- path路径,资源在服务器中的指定地址
- parameters:参数不常用
- query:查询 查询某种资源
- fragment:片段 对资源描述的部分补充,相当于资源内部的书签:可用作单页面路由或者HTML锚点
http,https
http1.0/http1.1:目前的正式标准广泛使用,持久连接,节约宽带,管道机制,分块传输编码
http2.0:多路复用,服务器推送,头信息压缩,二进制协议,逐渐覆盖市场
https:http下加入SSL层进行加密,主打一个安全
request
request-method
- GET 请求中的参数包含在url中,数据可以在url中看到,最多提交1024字节
- POST 请求的URL不会包含这些数据,数据通过表达传输,包含在请求体中
- HEAD 类似于GET请求,但是返回的响应中没有具体内容,用于获取报头
- PUT 用客户端传向服务器的数据取代指定文档中的内容
- DELETE 请求服务器删除指定的页面
- CONNECT 把服务器当跳板,让服务器代替客户端访问其他网页
- OPTIONS 允许客户端查看服务器性能
- TRACE 回显服务器收到的请求,主要用于测试或者诊断
request-head
Accept:请求报头域,用于指定客户端可以接受哪些类型信息
Accept-Language:指定客户端可接受的语言类型
Accept-Encoding:指定客户端可解搜的内容编码
Host: 用于指定请求资源的主机IP和端口号
Cookie: 或写作Cookies,为了辨别用户,进行会话跟踪而存储在用户本地的数据,主要功能是维持当前访问会话,保存登录信息
Referer: 用于标识请求从哪个页面发过来的,用作来源统计或者防盗链处理
User-Agent:UA头,使服务器识别客户端使用的操作系统及版本,浏览器及版本等信息,爬虫凭此可伪装成浏览器
Content-Type: 互联网媒体类型,text/html代表html格式,image/git代表gif图片,application/json 代表json类型
Upgrade-Insecure-Requests:一个请求首部,用来向服务器端发送信号,表示客户端优先选择加密及带有身份验证的响应,并且它可以成功处理 upgrade-insecure-requests CSP (en-US) 指令。
Sec-Fetch-*:Sec-Fetch-*请求头,了解下? - 掘金 (juejin.cn)
eg:访问baidu.com的请求头
request-body
GET:请求体为空
POST: 数据在请求体中,数据以内容表单数据的形式提交给服务器。requestheader的Content-Type 为 application/x-www-form-urlencoded POST提交数据方式为:表达数据 multipart/form-data 表单文件上传 application/json 序列化json数据 text/xml XML表单数据
response
status-code
2XX 成功
200 ok(请求成功)
204 no content (请求成功,但是没有结果返回)
206 partial content (客户端请求一部分资源,服务端成功响应,返回一范围资源)
3XX 重定向
301 move permanently (永久性重定向)
302 found (临时性重定向)
303 see other (示由于请求对应的资源存在着另一个 URI,应使用 GET
方法定向获取请求的资源)
304 not modified (表示在客户端采用带条件的访问某资源时,服务端找到了资源,但是这个请求的条件不符合。跟重定向无关)
307 temporary redirect (跟302一个意思)
4XX 客户端错误
400 bad request (请求报文存在语法错误)
401 unauthorized (需要认证(第一次返回)或者认证失败(第二次返回))
403 forbidden (请求被服务器拒绝了)
404 not found (服务器上无法找到请求的资源)
5XX 服务器错误
500 internal server error (服务端执行请求时发生了错误)
503 service unavailable (服务器正在超负载或者停机维护,无法处理请求)
其他不常见的请见HTTP 状态码 - 掘金 (juejin.cn)
response-header
Data: 标识响应产生的时间
Last-Modified: 指定资源的最后修改时间
Content-Encoding: 指定响应内容的编码
Server: 包含服务器的信息,例如名称和版本号
Content-Type: 文档类型,指定返回的数据是什么类型text/html:html文档,application/x-javascript返回javascript类型,image/jpeg返回图片
Set-Cookie:设置Cookie,告诉浏览器需要将此内容放在Cooki中
Expires:用于指定响应的过期时间
response-body
即响应的正文数据,是我们需要爬取的内容如图下
urllib
llib 包 包含以下几个模块:
- urllib.request - 打开和读取 URL。
- urllib.error - 包含 urllib.request 抛出的异常。
- urllib.parse - 解析 URL。
- urllib.robotparser - 解析 robots.txt 文件。
urllib.request
urllib.request.urlopen(url, data=None, [timeout, ]*, cafile=None, capath=None, cadefault=False, context=None)
- url:url 地址。
- data:发送到服务器的其他数据对象,默认为 None。
- timeout:设置访问超时时间。
- cafile 和 capath:cafile 为 CA 证书, capath 为 CA 证书的路径,使用 HTTPS 需要用到。
- cadefault:已经被弃用。
- context:ssl.SSLContext类型,用来指定 SSL 设置。
模拟网页打开
import urllib.request
response=urllib.request.urlopen('www.baidu.com')#打开一个网页,并返回网页源码
response.status #返回网页响应状态码
response.getheaders()#返回网页的响应头信息
response.getheaders('Server')#返回响应头信息'Server'的信息
#headers可获取的值是响应头的所有元素,eg:Content-Type:指定服务器返回的响应内容类型。
response.read().decode('utf-8')#输出utf8编码的源代码
response.read(300)#指定输出的长度
response.readline()#读取一行内容
response.readlines()#读取全部内容,返回一个列表,每一行是列表的元素
encoded_url=urllib.request.quote('www.baidu.com')#编码,unquote就是url解码
模拟头部信息则需要urllib.request.Request类
class urllib.request.Request(url, data=None, headers={}, origin_req_host=None, unverifiable=False, method=None)
- url:url 地址。
- data:发送到服务器的其他数据对象,默认为 None。
- headers:HTTP 请求的头部信息,字典格式。
- origin_req_host:请求的主机地址,IP 或域名。
- unverifiable:很少用整个参数,用于设置网页是否需要验证,默认是False。。
- method:请求方法, 如 GET、POST、DELETE、PUT等。
import urllib.request
import urllib.parse
header = {
'User-Agent':'Mozilla/5.0 (X11; Fedora; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36'
} #头部信息
url='www.baidu.com/?s='
keyword='Python教程'
url_all=url+urllib.request.quote(keyword)
request=urllib.request.Request(url_all,headers=header)#构造请求头
response=urllib.request.urlopen(request).read()#输出响应体
url = 'https://www.runoob.com/try/py3/py3_urllib_test.php' # 提交到表单页面
data = {'name':'RUNOOB', 'tag' : '菜鸟教程'} # 提交数据
data=urllib.parse.urlencode(data).encode('utf8')
request=urllib.request.Request(url,data,header,method='POST')#请求处理
response=urllib.request.urlopen(request).read()#输出响应体
req.add_header('xxx','wwww')#添加文件头
urllib.error
urllib.error 模块为 urllib.request 所引发的异常定义了异常类,基础异常类是 URLError。
urllib.error 包含了两个方法,URLError 和 HTTPError。
URLError 是 OSError 的一个子类,用于处理程序在遇到问题时会引发此异常(或其派生的异常),包含的属性 reason 为引发异常的原因。
HTTPError 是 URLError 的一个子类,用于处理特殊 HTTP 错误例如作为认证请求的时候,包含的属性 code 为 HTTP 的状态码, reason 为引发异常的原因,headers 为导致 HTTPError 的特定 HTTP 请求的 HTTP 响应头。
对不存在的网页抓取并处理异常:
import urllib.request
import urllib.error
myurl=urllib.request.urlopen('www.baidu.com/404.html')
try:
myurl2=urllib.request.urlopen(myurl)
except urllib.error.HTTPError as e:
if e.code==404:
print(404)
urllib.parse
解析url
urllib.parse.urlparse(urlstring, scheme=‘’, allow_fragments=True)
- urlstring:url地址
- scheme:协议类型
- allow_fragments: 是否忽略fragment
from urllib.parse import urlparse
o = urlparse("https://www.runoob.com/?s=python+%E6%95%99%E7%A8%8B")
print(o)
#ParseResult(scheme='https', netloc='www.runoob.com', path='/', params='', query='s=python+%E6%95%99%E7%A8%8B', fragment='')
- urllib.parse.urlunparse:用于构造url
import urllib.parse
data=['https','www.baidu.com','index.html','user','a=6','comment']
urllib.unparse(data)
#https://www.baidu.com/index.html;user?a=6#comment
-
urllib.parse.urlsplit:类型与urlparse 不过不单独解析params,合并到path
-
urllib.parse.urljoin:提供一个base_url作为第一个参数,将新的连接作为第二个参数,urljoin会分析base_url的scheme,netloc,path三个内容,对新连接确实的部分进行补充,然后返回结果
baseurl提供scheme,netloc,path若新连接不存在这三项,则补充,否则使用新链接的
-
urllib.parse.encode:构造get请求
-
urllib.parse.parse_qs 反序列化
-
urllib.parse.parse_qsl 参数转元组组成的列表
-
urllib.parse.quote 转化url编码格式
-
urllib.parse.unquote(url) 将url解码
urllib.robotparser
不常用
class urllib.robotparser.RobotFileParser(url=‘’)
>>> import urllib.robotparser
>>> rp = urllib.robotparser.RobotFileParser()
>>> rp.set_url("http://www.musi-cal.com/robots.txt")
>>> rp.read()
>>> rrate = rp.request_rate("*")
>>> rrate.requests
3
>>> rrate.seconds
20
>>> rp.crawl_delay("*")
6
>>> rp.can_fetch("*", "http://www.musi-cal.com/cgi-bin/search?city=San+Francisco")
False
>>> rp.can_fetch("*", "http://www.musi-cal.com/")
True
handler
-
设置代理
from urllib.error import URLError from urllib.request import ProxyHandler,build_opener #Request相当于一个特殊的opener类 proxy_handler=ProxyHandler({ 'http':'http://127.0.0.1:8080', 'https':'https://127.0.0.1:8080' }) opener=build_opener(proxy_handler) try: response=opener.open('https://www.baidu.com') #opener提供open方法,与urlopen如出一辙 print(response.read().decode('utf8')) except URLError as e: print(e.reason) #打印httperror的状态
-
身份验证
from urllib.request import HTTPPasswordMgrWithDefaultRealm,HTTPBasicAuthHandler,build_opener from urllib.error import URLError username='admin' password='admin' url='https://ssr3.scrape.center/' p=HTTPPasswordMgrWithDefaultRealm() p.add_password(None,url,username,password)#账户对象 auth_handler=HTTPBasicAuthHandler(p) opener=build_opener(auth_handler) try: result=opener.open(url) html=result.read().decode('utf-8') print(html) except URLError as e: print(e.reason)
-
设置Cookie
import http.cookiejar,urllib.request cookie = http.cookiejar.CookieJar()#声明一个CookieJar对象 handler = urllib.request.HTTPCookieProcessor(cookie) opener=urllib.request.build_opener(handler) response=opener.open("http://www.baidu.com") for item in cookie: print(item.name+'='+item.value)
requests
对比起urllib库实则是方便太多了,但是urllib是标准库捏,还是要学一下
,所有数据体返回体基本都是json格式
-
解析json
import requests import json response = requests.get("http://httpbin.org/get") print(type(response.text)) print(response.json()) print(json.loads(response.text)) print(type(response.json()))
返回值:
<class 'str'> {'args': {}, 'headers': {'Accept': '*/*', 'Accept-Encoding': 'gzip, deflate', 'Connection': 'close', 'Host': 'httpbin.org', 'User-Agent': 'python-requests/2.18.4'}, 'origin': '183.64.61.29', 'url': 'http://httpbin.org/get'} {'args': {}, 'headers': {'Accept': '*/*', 'Accept-Encoding': 'gzip, deflate', 'Connection': 'close', 'Host': 'httpbin.org', 'User-Agent': 'python-requests/2.18.4'}, 'origin': '183.64.61.29', 'url': 'http://httpbin.org/get'} <class 'dict'>
-
抓数据
import requests
r=requests.get('http://httpbin.org/get')#实现GET请求 同理.put .post .delete .patch实现其他请求
#get请求添加额外信息
data={
'name':'germey'
'age':25
}
#传入的数据是json格式的
r=requests.get('https://www.httpbin.org/get',params=date)
#网页返回的类型是str类型且是json格式的
demo:爬取所有电影名
import requests
import re
r=requests.get('https://ssr1.scrape.center/')
pattern=re.compile('<h2.*?>(.*?)</h2>',re.S)
titles=re.findall(pattern,r.text)
print(titles)
demo2:抓取二进制数据,一半用于爬取图片,以及视频
import requests
r=requests.get('https://scrape.center/favicon.ico')
print(r.text) #图片的二进制流转为str类型
print(r.content) #返回bytes类型的数据
- 添加请求头
headers={
'User-Agent':'???'
}
r=requests.get('www.baidu.com',headers=headers)
print(r.text)
- 响应
r=requests.get('www.baidu.com')
r.status_code#状态码
r.headers #响应头
r.cookies #cookie
r.url #url
r.histroy #请求历史
- 文件上传
模拟上传文件
demo:
import requests
files={'file':open('favicon.ico','rb')}
r=requests.post('https://www.httpbin.org/post',file=files)
print(r.text)#返回上传文件后的响应
- 设置Cookies
只需要在headers的字典中添加’Cookie’:'xxx’即可
- 维持Seesion
不设置Seesion就相当于两次爬取打开两个浏览器,Cookie就无法保持
设置了一个Seesion,可以提高爬取速度而且维持Cookies
s = requests.Session()#创建一个Session对象
s.get('https://httpbin.org/cookies/set/sessioncookie/123456789')
r = s.get('https://httpbin.org/cookies')
print(r.text)
r.content和r.text都是requests库中Response对象的属性,用于获取请求的响应内容,但它们之间有一些重要的区别:
类型:
r.content返回的是以字节形式表示的响应内容,即bytes类型的对象。
r.text返回的是以Unicode形式表示的响应内容,即str类型的对象。
编码:
r.content返回的是原始的字节数据,没有进行任何编码,因此它适用于处理图片、音频、视频等二进制数据。
r.text返回的是根据HTTP响应中的字符编码自动解码后的文本数据,通常是UTF-8编码。如果无法确定编码方式,requests库会根据HTTP头部中的字符编码来猜测,或者使用默认的UTF-8编码。
用途:
当需要处理文本内容时,如HTML页面或JSON数据等,通常使用r.text,因为它提供了方便的文本操作方法,比如字符串查找、分割等。
当需要处理二进制数据时,如图片、视频、文件等,通常使用r.content,因为它返回的是原始的字节数据,不会对数据进行额外的解析或处理。
-
超时设置
访问有些网站时可能会超时,这时设置好timeout就可以解决这个问题
import requests from requests.exceptions import ReadTimeout try: response = requests.get("http://httpbin.org/get", timeout = 0.5) print(response.status_code) except ReadTimeout: print('Timeout')
正常访问,状态吗返回200
-
异常处理
遇到网络问题(如:DNS查询失败、拒绝连接等)时,Requests会抛出一个ConnectionError 异常。
遇到罕见的无效HTTP响应时,Requests则会抛出一个 HTTPError 异常。
若请求超时,则抛出一个 Timeout 异常。
若请求超过了设定的最大重定向次数,则会抛出一个 TooManyRedirects 异常。
所有Requests显式抛出的异常都继承自 requests.exceptions.RequestException 。
re
正则教程:https://www.cnblogs.com/poloyy/category/1796055.html
正则语法
正则字符 | 描述 |
---|---|
\ | 将下一个字符标记为一个特殊字符、或一个原义字符、或一个向后引用、或一个八进制转义符。例如,“n "匹配字符"n "。“\n "匹配一个换行符。串行”\\ “匹配”\ “而”\( “则匹配”( "。 |
^ | 匹配输入字符串的开始位置。如果设置了RegExp对象的Multiline属性,^也匹配“\n "或"\r "之后的位置。 |
$ | 匹配输入字符串的结束位置。如果设置了RegExp对象的Multiline属性,$也匹配“\n "或"\r "之前的位置。 |
* | 匹配前面的子表达式零次或多次。例如,zo*能匹配“z "以及"zoo "。*等价于{0,}。 |
+ | 匹配前面的子表达式一次或多次。例如,“zo+ “能匹配"zo "以及"zoo ”,但不能匹配"z "。+等价于{1,}。 |
? | 匹配前面的子表达式零次或一次。例如,“do(es)? “可以匹配"does "或"does "中的"do ”。?等价于{0,1}。 |
{n} | n是一个非负整数。匹配确定的n次。例如,“o{2} “不能匹配"Bob "中的"o ”,但是能匹配"food "中的两个o。 |
{n,} | n是一个非负整数。至少匹配n次。例如,“o{2,} “不能匹配"Bob "中的"o ”,但能匹配"foooood "中的所有o。"o{1,} “等价于"o+ ”。"o{0,} “则等价于"o* ”。 |
{n,m} | m和n均为非负整数,其中n<=m。最少匹配n次且最多匹配m次。例如,“o{1,3} "将匹配"fooooood "中的前三个o。"o{0,1} “等价于"o? ”。请注意在逗号和两个数之间不能有空格。 |
? | 当该字符紧跟在任何一个其他限制符(*,+,?,{n},{n,},{n,m})后面时,匹配模式是非贪婪的。非贪婪模式尽可能少的匹配所搜索的字符串,而默认的贪婪模式则尽可能多的匹配所搜索的字符串。例如,对于字符串“oooo ","o+? “将匹配单个"o ”,而"o+ “将匹配所有"o ”。 |
. | 匹配除“\ n "之外的任何单个字符。要匹配包括"\ n "在内的任何字符,请使用像"`(. |
(pattern) | 匹配pattern并获取这一匹配。所获取的匹配可以从产生的Matches集合得到,在VBScript中使用SubMatches集合,在JScript中则使用$0…$9属性。要匹配圆括号字符,请使用“\( “或”\) "。 |
(?:pattern) | 匹配pattern但不获取匹配结果,也就是说这是一个非获取匹配,不进行存储供以后使用。这在使用或字符“`( |
(?=pattern) | 正向肯定预查,在任何匹配pattern的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如,“`Windows(?=95 |
(?!pattern) | 正向否定预查,在任何不匹配pattern的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如“`Windows(?!95 |
(?<=pattern) | 反向肯定预查,与正向肯定预查类拟,只是方向相反。例如,“`(?<=95 |
(?<!pattern) | 反向否定预查,与正向否定预查类拟,只是方向相反。例如“`(?<!95 |
x|y | 匹配x或y。例如,“`z |
[xyz] | 字符集合。匹配所包含的任意一个字符。例如,“[abc] “可以匹配"plain "中的"a ”。 |
[^xyz] | 负值字符集合。匹配未包含的任意字符。例如,“[^abc] “可以匹配"plain "中的"p ”。 |
[a-z] | 字符范围。匹配指定范围内的任意字符。例如,“[a-z] "可以匹配"a "到"z "范围内的任意小写字母字符。 |
[^a-z] | 负值字符范围。匹配任何不在指定范围内的任意字符。例如,“[^a-z] "可以匹配任何不在"a "到"z "范围内的任意字符。 |
\b | 匹配一个单词边界,也就是指单词和空格间的位置。例如,“er\b "可以匹配"never "中的"er ",但不能匹配"verb "中的"er "。 |
\B | 匹配非单词边界。“er\B "能匹配"verb "中的"er ",但不能匹配"never "中的"er "。 |
\cx | 匹配由x指明的控制字符。例如,\cM匹配一个Control-M或回车符。x的值必须为A-Z或a-z之一。否则,将c视为一个原义的“c "字符。 |
\d | 匹配一个数字字符。等价于[0-9]。 |
\D | 匹配一个非数字字符。等价于[^0-9]。 |
\f | 匹配一个换页符。等价于\x0c和\cL。 |
\n | 匹配一个换行符。等价于\x0a和\cJ。 |
\r | 匹配一个回车符。等价于\x0d和\cM。 |
\s | 匹配任何空白字符,包括空格、制表符、换页符等等。等价于[ \f\n\r\t\v]。 |
\S | 匹配任何非空白字符。等价于[^ \f\n\r\t\v]。 |
\t | 匹配一个制表符。等价于\x09和\cI。 |
\v | 匹配一个垂直制表符。等价于\x0b和\cK。 |
\w | 匹配包括下划线的任何单词字符。等价于“[A-Za-z0-9_] "。 |
\W | 匹配任何非单词字符。等价于“[^A-Za-z0-9_] "。 |
\xn | 匹配n,其中n为十六进制转义值。十六进制转义值必须为确定的两个数字长。例如,“\x41 "匹配"A "。“\x041 "则等价于”\x04&1 "。正则表达式中可以使用ASCII编码。. |
*num* | 匹配num,其中num是一个正整数。对所获取的匹配的引用。例如,“(.)\1 "匹配两个连续的相同字符。 |
*n* | 标识一个八进制转义值或一个向后引用。如果*n之前至少n个获取的子表达式,则n为向后引用。否则,如果n为八进制数字(0-7),则n*为一个八进制转义值。 |
*nm* | 标识一个八进制转义值或一个向后引用。如果*nm之前至少有nm个获得子表达式,则nm为向后引用。如果*nm之前至少有n个获取,则n为一个后跟文字m的向后引用。如果前面的条件都不满足,若n和m均为八进制数字(0-7),则*nm将匹配八进制转义值nm*。 |
*nml* | 如果n为八进制数字(0-3),且m和l均为八进制数字(0-7),则匹配八进制转义值nml。 |
\un | 匹配n,其中n是一个用四个十六进制数字表示的Unicode字符。例如,\u00A9匹配版权符号 |
常见正则表达式运算符的优先级:
-
转义符号:
\
是用于转义其他特殊字符的转义符号。它具有最高的优先级。示例:
\d
、\.
等,其中\d
匹配数字,\.
匹配点号。 -
括号: 圆括号
()
用于创建子表达式,具有高于其他运算符的优先级。示例:
(abc)+
匹配 “abc” 一次或多次。 -
量词: 量词指定前面的元素可以重复的次数。
示例:
a*
匹配零个或多个 “a”。 -
字符类: 字符类使用方括号
[]
表示,用于匹配括号内的任意字符。示例:
[aeiou]
匹配任何一个元音字母。 -
断言: 断言是用于检查字符串中特定位置的条件的元素。
示例:
^
表示行的开头,$
表示行的结尾。 -
连接: 连接在没有其他运算符的情况下表示字符之间的简单连接。
示例:
abc
匹配 “abc”。 -
管道: 管道符号
|
表示"或"关系,用于在多个模式之间选择一个。示例:
cat|dog
匹配 “cat” 或 “dog”。
常用正则
target | pattern |
---|---|
用户名 | /^[a-z0-9_-]{3,16}$/ |
密码 | /^[a-z0-9_-]{6,18}$/ |
密码2 | (?=^.{8,})(?=.*\d)(?=.*\W+)(?=.*[A-Z])(?=.*[a-z])(?!.*\n).* (由数字/大写字母/小写字母/标点符号组成,四种都必有,8位以上) |
十六进制值 | /^#?([a-f0-9]{6}|[a-f0-9]{3})$/ |
电子邮箱 | /^([a-z0-9_.-]+)@([\da-z.-]+).([a-z.]{2,6})/ /^[a-z\d]+(\.[a-z\d]+)*@([\da-z](-[\da-z])?)+(\.{1,2}[a-z]+)+/或\w+([-+.]\w+)@\w+([-.]\w+).\w+([-.]\w+)* |
URL | /^(https?://)?([\da-z.-]+).([a-z.]{2,6})([/\w .-])/?$/ 或 [a-zA-z]+://[^\s]* |
IP 地址 | /((2[0-4]\d|25[0-5]|[01]?\d\d?).){3}(2[0-4]\d|25[0-5]|[01]?\d\d?)/ /^(?😦?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?).){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/ 或 ((2[0-4]\d|25[0-5]|[01]?\d\d?).){3}(2[0-4]\d|25[0-5]|[01]?\d\d?) |
HTML 标签 | /<([a-z]+)([<]+)(?:>(.)</\1>|\s+/>)$/或<(.)(.)>.</\1>|<(.) /> |
删除代码\注释 | (?<!http:|\S)//.*$ |
匹配双字节字符(包括汉字在内) | [^\x00-\xff] |
汉字(字符) | [\u4e00-\u9fa5] |
Unicode编码中的汉字范围 | /^[\u2E80-\u9FFF]+$/ |
中文及全角标点符号(字符) | [\u3000-\u301e\ufe10-\ufe19\ufe30-\ufe44\ufe50-\ufe6b\uff01-\uffee] |
日期(年-月-日) | (\d{4}|\d{2})-((0?([1-9]))|(1[1|2]))-((0?[1-9])|(12)|(3[0|1])) |
日期(月/日/年) | ((0?[1-9]{1})|(1[1|2]))/(0?[1-9]|([12][1-9])|(3[0|1]))/(\d{4}|\d{2}) |
时间(小时:分钟, 24小时制) | ((1|0?)[0-9]|2[0-3])😦[0-5][0-9]) |
中国大陆固定电话号码 | (\d{4}-|\d{3}-)?(\d{8}|\d{7}) |
中国大陆手机号码 | 1\d{10} |
中国大陆邮政编码 | [1-9]\d{5} |
中国大陆身份证号(15位或18位) | \d{15}(\d\d[0-9xX])? |
非负整数(正整数或零) | \d+ |
正整数 | [0-9][1-9][0-9] |
负整数 | -[0-9][1-9][0-9] |
整数 | -?\d+ |
小数 | (-?\d+)(.\d+)? |
空白行 | \n\s*\r 或者 \n\n(editplus) 或者 ^[\s\S ]*\n |
QQ号码 | [1-9]\d{4,} |
不包含abc的单词 | \b((?!abc)\w)+\b |
匹配首尾空白字符 | ^\s*|\s*$ |
编辑常用 | 以下是针对特殊中文的一些替换(editplus)^[0-9].\n [第].\n [习题].*\n[\s\S ]\n ^[0-9]. ^[\s\S ]\n <p[^<>]>href=“javascript:if(confirm(‘(.?)‘))window.location=’(.?)’”<span style=".[“]*rgb(255,255,255)”>.[<>] [\s\S]? |
re库
参考网址:https://zhuanlan.zhihu.com/p/565487182
re模块官方文档:https://docs.python.org/zh-cn/3.8/library/re.html
正则的匹配模式
flags : 可选,表示匹配模式,比如忽略大小写,多行模式等,具体参数为:
- re.I re.IGNORECASE忽略大小写
- re.L re.LOCALE表示特殊字符集 w, W, , B, s, S 依赖于当前环境
- re.M re.MULTILINE多行模式
- re.S re.DOTALL 即为 . 并且包括换行符在内的任意字符(. 不包括换行符)
- re.U 表示特殊字符集 w, W, , B, d, D, s, S 依赖于 Unicode 字符属性数据库
- re.X re.VERBOSE为了增加可读性,忽略空格和 # 后面的注释,可以在正则表达式中加注解
- re.A re.ASCII 让 \w, \W, \b, \B, \d, \D, \s 和 \S 只匹配ASCII,而不是Unicode。
单项查找函数
re.search(pattern,string,flags=0) 三个函数参数相同
- pattern 匹配规则
- string 匹配内容
- flags 匹配模式
- re.search: 会匹配整个字符串,并返回第一个成功的匹配。如果匹配失败,则返回None
- re.match: 必须从字符串开头匹配
- re.fullmatch: 整个字符串与正则完全匹配
#search实例
import re
str='ab12cd34ef567'
info=re.compile(r'([0-9]{2})')#[0-9]{2}表示0-9的任意数字匹配2次,()代表作为一组返回
result=info.search(str).group()
print(result)#('12',) 每次只返回一个,但是如果第二次返回就是('12','34')
#match实例
import re
str='ab12cd34ef567'
info=re.compile(r'([a-z]{2})(\d{2})')#group(0)表示a-z的两个任意字符,\d{2}表示数字的两个任意字符
result=info.match(str).group() #ab12
result1=info.match(str).groups()#('ab','12')
#fullmatch实例-比match限制更严格
import re
string="MyTools"
pattern='My'
re.match(pattern,string)
re.fullmatch(pattern,string)
多项查找函数
- findall: 从字符串任意位置查找,返回一个列表
- finditer:从字符串任意位置查找,返回一个迭代器
如果可能存在大量的匹配项的话,建议使用finditer函数,一般情况使用findall函数基本没啥影响。列表是一次性生成在内存中,而迭代器是需要使用时一点一点生成出来的,内存使用更优。
#findall实例
s='MyToolsMyCompueter'
m=re.findall('M',s)
print(m) #['M','M']
#finditer实例
it=re.finditer(r"\d+","12a32bc43jf3")
for match in it:
print(match.group())
#12 32 43 3
分割函数
re.split(pattern, string, maxsplit=0, flags=0)
- pattern 分割string的正则匹配规则
- maxsplit 最多分割次数
- string 目标字符串
- flags 匹配模式
str.split与re.split的选择:str.split函数功能简单,不支持正则分割,而re.split支持正则。
1000次循环以内str.split函数更快,而循环次数1000次以上后re.split函数明显更快,而且次数越多差距越大!
在不需要正则支持且数据量和数次不多的情况下使用str.split函数更合适,反之则使用re.split函数。
#split实例
s='abc,abc,defg,dds'
re.split('(\W+)',s)#匹配,分割
#['abc', ', ', 'abc', ', ', 'defg', ', ', 'dds']
替换函数
re.sub(pattern, repl, string, count=0, flags=0)
- pattern 匹配表达式
- repl 用于替换掉string被pattern匹配的字符
- count 表示替换次数
- flags 匹配规则
re.subn:参数相同但是返回的一个元组(替换后的字符串,替换次数)
值得注意的是:sub函数中的入参:repl替换内容既可以是字符串,也可以是一个函数哦! 如果repl为函数时,只能有一个入参:Match匹配对象。
#sub实例 target:将匹配到结果中小于4的替换成8,大于4的替换成9
import re
def modify(value):
matched=value.group()
if int(matched) <=4:
return '8'
else:
return '9'
str='ab12cd34ef567'
result=re.sub('\d',modify,str)
#ab88cd88ef999
#subn实例
import re
print(re.subn('ov', '~*' , 'movie tickets booking in online'))
#('m~*ie tickets booking in online', 1)
编译函数
compile函数
compile函数将正则表达式的样式编译为一个正则表达式对象(Pattern),且与re模块相同的函数。
用re模块还是正则对象Pattern的选择:
官方文档推荐:在多次使用某个正则表达式时推荐使用正则对象Pattern 以增加复用性,因为通过 re.compile(pattern) 编译后的模块级函数会被缓存!
re.escape(pattern) 可以转义正则表达式中具有特殊含义的字符,比如:. 或者 *
re.purge()不常用,清楚正则缓存
css-selector
基本选择器
-
选择所有元素。(可选)可以将其限制为特定的名称空间或所有名称空间。 语法:
*
ns|*
*|*
例子:*
将匹配文档的所有元素。 -
按照给定的节点名称,选择所有匹配的元素。 语法:
elementname
例子:input
匹配任何 `` 元素。 -
按照给定的
class
属性的值,选择所有匹配的元素。 语法:.classname
例子:.index
匹配任何class
属性中含有 “index” 类的元素。 -
按照
id
属性选择一个与之匹配的元素。需要注意的是,一个文档中,每个 ID 属性都应当是唯一的。 语法:#idname
例子:#toc
匹配 ID 为 “toc” 的元素。 -
按照给定的属性,选择所有匹配的元素。 语法:
[attr]
[attr=value]
[attr~=value]
[attr|=value]
[attr^=value]
[attr$=value]
[attr*=value]
例子:[autoplay]
选择所有具有autoplay
属性的元素(不论这个属性的值是什么)。
分组选择器(Grouping selector)
-
,
是将不同的选择器组合在一起的方法,它选择所有能被列表中的任意一个选择器选中的节点。 语法:A, B
示例:div, span
会同时匹配](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/span) 元素和 [
元素。
组合器(Combinator)
-
“ ”(空格)组合器选择前一个元素的后代节点。 语法:
A B
例子:div span
匹配所有位于任意](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/div) 元素之内的 [
元素。 -
>
组合器选择前一个元素的直接子代的节点。 语法:A > B
例子:ul > li
匹配直接嵌套在](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/ul) 元素内的所有 [
元素。 -
~
组合器选择兄弟元素,也就是说,后一个节点在前一个节点后面的任意位置,并且共享同一个父节点。 语法:A ~ B
例子:p ~ span
匹配同一父元素下,](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/p) 元素后的所有 [
元素。 -
+
组合器选择相邻元素,即后一个元素紧跟在前一个之后,并且共享同一个父节点。 语法:A + B
例子:h2 + p
会匹配紧邻在 h2 元素后的第一个 `` 元素。 -
列组合器 实验性
||
组合器选择属于某个表格行的节点。 语法:A || B
例子:col || td
会匹配所有 作用域内的 ` (en-US)](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/td) 元素。
伪选择器(Pseudo)
-
:
伪选择器支持按照未被包含在文档树中的状态信息来选择元素。 例子:a:visited
匹配所有曾被访问过的 `` 元素。 -
::
伪选择器用于表示无法用 HTML 语义表达的实体。 例子:p::first-line
匹配所有 `` 元素的第一行。
httpx
httpx针对于爬取http2.0协议的网站
httpx官方文档:https://www.python-httpx.org/quickstart
httpx 和 requests 的API 极其相似
基本用法
- GET
import httpx
response=httpx.get('https://spa16.scrape.center')
response.status_code#状态码
response.headers#响应头 返回一个Headers对象,类似于一个字典
response.text #正文内容
#更换UserAgent
headers={
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:123.0) Gecko/20100101 Firefox/123.0'
}
response=httpx.get(url,headers=headers)
-
其他方法:httpx.get/post/put/delete/patch
-
Response对象的属性和方法
- status_code 状态码
- text 响应体的文本内容
- content 响应体的二进制内容
- headers 响应头
- json 方法,将文本结果转化为json对象
Client对象
可以与requests中的Session对象类比学习
client的更多高级用法:https://www.python-httpx.org/advanced
- 使用方式:比较推荐的使用方式:with as
import httpx
with httpx.client() as client:
response=client.get('https://www.httpbin.org/get')
print(response)
等价于
import httpx
client=httpx.Client()
try:
response=clinet.get('https://www.httpbin.org/get')
finally:
client(close)
-
填入参数 header=?
-
新参数http_version 若http2=True 则支持http2 默认是关闭的不支持http2
-
支持异步客户端请求
import httpx import asyncio #一个异步爬虫框架 async def fetch(url): async with httpx.AsyncClient(http2=True) as client: response=await client.get(url) print(response.text) asyncio.get_event_loop().run_until_complete(fetch('https://www.httpbin.org/get'))
X-Path
XPath的全程是XML PATH Language 即xml路径语言,用于在xml文档中 查找信息,但是同样适用于HTML文档的搜索
Xpath语法
xml实例
<?xml version="1.0" encoding="UTF-8"?>
<bookstore>
<book>
<title lang="eng">Harry Potter</title>
<price>29.99</price>
</book>
<book>
<title lang="eng">Learning XML</title>
<price>39.95</price>
</book>
</bookstore>
术语
在 XPath 中,有七种类型的节点:元素、属性、文本、命名空间、处理指令、注释以及文档(根)节点。XML 文档是被作为节点树来对待的。树的根被称为文档节点或者根节点。
基本值是无父或无子的节点。
项目是基本值或者节点
每个元素以及属性都有一个父。
元素节点可有零个、一个或多个子。
同胞:拥有相同的父的节点
先辈:某节点的父、父的父,等等
后代: 某个节点的子,子的子
轴:可定义相对于当前节点的节点集。
轴名称 | 结果 |
---|---|
ancestor | 选取当前节点的所有先辈(父、祖父等)。 |
ancestor-or-self | 选取当前节点的所有先辈(父、祖父等)以及当前节点本身。 |
attribute | 选取当前节点的所有属性。 |
child | 选取当前节点的所有子元素。 |
descendant | 选取当前节点的所有后代元素(子、孙等)。 |
descendant-or-self | 选取当前节点的所有后代元素(子、孙等)以及当前节点本身。 |
following | 选取文档中当前节点的结束标签之后的所有节点。 |
following-sibling | 选取当前节点之后的所有兄弟节点 |
namespace | 选取当前节点的所有命名空间节点。 |
parent | 选取当前节点的父节点。 |
preceding | 选取文档中当前节点的开始标签之前的所有节点。 |
preceding-sibling | 选取当前节点之前的所有同级节点。 |
self | 选取当前节点。 |
选取节点
表达式 | 描述 |
---|---|
nodename | 读取此节点的所有子节点 |
/ | 从当前节点选取直接子节点 |
// | 从当前节点选取子孙节点 |
. | 选取当前节点 |
… | 选取当前节点的父节点 |
@ | 选取属性 |
eg://title[@lang=‘eng’] 表示 所有名称为title,同时属性lang的值为eng的节点
路径表达式 | 结果 |
---|---|
bookstore | 选取 bookstore 元素的所有子节点。 |
/bookstore | 选取根元素 bookstore。 注释:假如路径起始于正斜杠( / ),则此路径始终代表到某元素的绝对路径! |
bookstore/book | 选取属于 bookstore 的子元素的所有 book 元素。 |
//book | 选取所有 book 子元素,而不管它们在文档中的位置。 |
bookstore//book | 选择属于 bookstore 元素的后代的所有 book 元素,而不管它们位于 bookstore 之下的什么位置。 |
//@lang | 选取名为 lang 的所有属性。 |
谓语(Predicates)
谓语用来查找某个特定的节点或者包含某个指定的值的节点。
谓语被嵌在方括号中。
在下面的表格中,我们列出了带有谓语的一些路径表达式,以及表达式的结果:
路径表达式 | 结果 |
---|---|
/bookstore/book[1] | 选取属于 bookstore 子元素的第一个 book 元素。 |
/bookstore/book[last()] | 选取属于 bookstore 子元素的最后一个 book 元素。 |
/bookstore/book[last()-1] | 选取属于 bookstore 子元素的倒数第二个 book 元素。 |
/bookstore/book[position()❤️] | 选取最前面的两个属于 bookstore 元素的子元素的 book 元素。 |
//title[@lang] | 选取所有拥有名为 lang 的属性的 title 元素。 |
//title[@lang=‘eng’] | 选取所有 title 元素,且这些元素拥有值为 eng 的 lang 属性。 |
/bookstore/book[price>35.00] | 选取 bookstore 元素的所有 book 元素,且其中的 price 元素的值须大于 35.00。 |
/bookstore/book[price>35.00]//title | 选取 bookstore 元素中的 book 元素的所有 title 元素,且其中的 price 元素的值须大于 35.00。 |
选取未知节点
XPath 通配符可用来选取未知的 XML 元素。
通配符 | 描述 |
---|---|
* | 匹配任何元素节点。 |
@* | 匹配任何属性节点。 |
node() | 匹配任何类型的节点。 |
在下面的表格中,我们列出了一些路径表达式,以及这些表达式的结果:
路径表达式 | 结果 |
---|---|
/bookstore/* | 选取 bookstore 元素的所有子元素。 |
//* | 选取文档中的所有元素。 |
//title[@*] | 选取所有带有属性的 title 元素。 |
选取若干路径
通过在路径表达式中使用"|"运算符,您可以选取若干个路径。
在下面的表格中,我们列出了一些路径表达式,以及这些表达式的结果:
路径表达式 | 结果 |
---|---|
//book/title | //book/price | 选取 book 元素的所有 title 和 price 元素。 |
//title | //price | 选取文档中的所有 title 和 price 元素。 |
/bookstore/book/title | //price | 选取属于 bookstore 元素的 book 元素的所有 title 元素,以及文档中所有的 price 元素。 |
运算符
运算符 | 描述 | 实例 | 返回值 |
---|---|---|---|
| | 计算两个节点集 | //book | //cd | 返回所有拥有 book 和 cd 元素的节点集 |
+ | 加法 | 6 + 4 | 10 |
- | 减法 | 6 - 4 | 2 |
* | 乘法 | 6 * 4 | 24 |
div | 除法 | 8 div 4 | 2 |
= | 等于 | price=9.80 | 如果 price 是 9.80,则返回 true。 如果 price 是 9.90,则返回 false。 |
!= | 不等于 | price!=9.80 | 如果 price 是 9.90,则返回 true。 如果 price 是 9.80,则返回 false。 |
< | 小于 | price<9.80 | 如果 price 是 9.00,则返回 true。 如果 price 是 9.90,则返回 false。 |
<= | 小于或等于 | price<=9.80 | 如果 price 是 9.00,则返回 true。 如果 price 是 9.90,则返回 false。 |
> | 大于 | price>9.80 | 如果 price 是 9.90,则返回 true。 如果 price 是 9.80,则返回 false。 |
>= | 大于或等于 | price>=9.80 | 如果 price 是 9.90,则返回 true。 如果 price 是 9.70,则返回 false。 |
or | 或 | price=9.80 or price=9.70 | 如果 price 是 9.80,则返回 true。 如果 price 是 9.50,则返回 false。 |
and | 与 | price>9.00 and price<9.90 | 如果 price 是 9.80,则返回 true。 如果 price 是 8.50,则返回 false。 |
mod | 计算除法的余数 | 5 mod 2 | 1 |
lxml库
可以通过xml库,利用XPath对HTML进行解析
demo1:
from lxml import etree
import requests
text = requests.get('http://quotes.toscrape.com').text
html = etree.HTML(text) #声明text是一段HTML文本,然后调用HTML类进行初始化,html就构成了一个XPath解析对象
#另一种不声明对象的html解析方法
html=etree.parse(text,etree.HTMLParse())
results=etree.tostring(html) #调用tostring的方法,可以自动修正HTML文本,返回的是bytes类型
print(results.decode('utf-8'))
对results进行html的xpath解析实例
result1=html.xpath('//*')
rresult2=
print(result1)
print(result1[0])
一些xpath表达式的解析
//a[@href="link1.html"]/../@ class #首先选中href属性为link4.html的a节点,然后获取其父节点,再获取父节点的class属性
//li[@class="item-0"]/text() #先选取li节点,然后利用/选取其直接子节点a,在选取节点a 的文本
>/bookstore/book[price>35]/title #选取价格高于35的title节点
>//li[contains(@class,"li")]/a/text() #给第一个参数传入属性名称,第二个参数传入属性值 >contains方法经常用于在某个节点的某个属性有多个值的时候用到 **运用lxml库的关键就是熟练掌握xpath选择语法** ## beautifulsoup https://beautifulsoup.cn/ ```python import requests from bs4 import BeautifulSoup #这是beautifulSoup的基本使用 url="http://quotes.toscrape.com/" html=requests.get(url).text soup=BeautifulSoup(html,'lxml')#对html源码初始化一个bs对象 print(soup.prettify()) #以标准缩进显示 print(soup.title) #打印title节点的选择结果 print(soup.title.string)#打印title节点的文本内容 print(type(soup))#这是bs4.element.Tag 类型 print(soup.p)#结果是第一个p节点的内容 print(soup.head)#结果是head节点的所有内容 #获取节点的信息 print(soup.p.attrs) #调用attrs获取p的所有属性 print(soup.p.string)#获取p节点的文本 print(soup.p.contents)#获取p的直接子节点
itertools
infinite iterators
count([start=0, step=1]) 接收两个可选整形参数,第一个指定了迭代开始的值,第二个指定了迭代的步长。此外,start参数默认为0,step参数默认为1,可以根据需要来把这两个指定为其它值,或者使用默认参数。
import itertools
for i in itertools.count(10,2):
print(i)
if i>20:
break
[Running] python -u "e:\pythonee\code\test.py"
10
12
14
16
18
20
22
cycle(iterable) 是用一个可迭代对象中的元素来创建一个迭代器,并且复制自己的值,一直无限的重复下去。
import itertools
for i in itertools.cycle("abcd"):
print(i) # 具有无限的输出,可以按ctrl+c来停止。
[Running] python -u "e:\pythonee\code\test.py"
a
b
c
d
a
b
c
d
a
b
**repeat(elem [,n])**是将一个元素重复n遍或者无穷多遍,并返回一个迭代器。
import itertools
for i in itertools.repeat("abcd",5):
print(i)
[Running] python -u "e:\pythonee\code\test.py"
abcd
abcd
abcd
abcd
abcd
combinatoric iterators
组合操作包括排列,笛卡儿积,或者一些离散元素的选择,组合迭代器就是产生这样序列的迭代器。我们来看看这几个函数的用法。
product(*iterables, repeat=1) 得到的是可迭代对象的笛卡儿积,*iterables参数表示需要多个可迭代对象。这些可迭代对象之间的笛卡儿积,也可以使用for循环来实现,例如 product(A, B) 与 **((x,y) for x in A for y in B)**就实现一样的功能。
import itertools
for i in itertools.product([1,2,3],[4,5,6]):
print(i)
[Running] python -u "e:\pythonee\code\test.py"
(1, 4)
(1, 5)
(1, 6)
(2, 4)
(2, 5)
(2, 6)
(3, 4)
(3, 5)
(3, 6)
而 repeat 参数则表示这些可迭代序列重复的次数。例如 product(A, repeat=4) 与 **product(A, A, A, A)**实现的功能一样
import itertools
for i in itertools.product('ab','cd',repeat = 2):
print(i)
[Running] python -u "e:\pythonee\code\test.py"
('a', 'c', 'a', 'c')
('a', 'c', 'a', 'd')
('a', 'c', 'b', 'c')
('a', 'c', 'b', 'd')
('a', 'd', 'a', 'c')
('a', 'd', 'a', 'd')
('a', 'd', 'b', 'c')
('a', 'd', 'b', 'd')
('b', 'c', 'a', 'c')
('b', 'c', 'a', 'd')
('b', 'c', 'b', 'c')
('b', 'c', 'b', 'd')
('b', 'd', 'a', 'c')
('b', 'd', 'a', 'd')
('b', 'd', 'b', 'c')
('b', 'd', 'b', 'd')
**permutations(iterable,r=None)**返回的是可迭代元素中的一个排列组合,并且是按顺序返回的
import itertools
for i in itertools.permutations('abc'):
print(i)
[Running] python -u "e:\pythonee\code\test.py"
('a', 'b', 'c')
('a', 'c', 'b')
('b', 'a', 'c')
('b', 'c', 'a')
('c', 'a', 'b')
('c', 'b', 'a')
当然,第 2 个参数默认为None,它表示的是返回元组(tuple) 的长度,我们来尝试一下传入第二个参数。
permutations函数当迭代对象出现相同元素时,是会产生重复结果
import itertools
for i in itertools.permutations('abc',2):
print(i)
[Running] python -u "e:\pythonee\code\test.py"
('a', 'b')
('a', 'c')
('b', 'a')
('b', 'c')
('c', 'a')
('c', 'b')
在Python的itertools模块中,permutations和product都是用于生成迭代器的函数,但它们有一些重要的区别:
元素的重复与顺序:
permutations函数生成的是可迭代对象,包含了原始序列中所有元素的所有可能的排列,但不包含重复的元素。排列的顺序取决于原始序列中元素的顺序。
product函数生成的是可迭代对象,包含了原始序列中所有元素的笛卡尔积,允许元素重复出现。其结果是原始序列中元素的所有可能的组合。
参数:
permutations函数接受两个参数:一个可迭代对象和一个整数(可选),指定生成排列的长度。如果不提供第二个参数,则默认生成与原始序列长度相同的排列。
product函数接受一个或多个可迭代对象作为参数,每个可迭代对象代表一个集合,它们的笛卡尔积将被计算。
Iterators Terminating on the Shortest Input Sequence
chain(*iterables) 可以把多个可迭代对象组合起来,形成一个更大的迭代器。
import itertools
for i in itertools.chain('good','bye'):
print(i)
[Running] python -u "e:\pythonee\code\test.py"
g
o
o
d
b
y
e
groupby(iterable,key=None) 可以把相邻元素按照 key 函数分组,并返回相应的 key 和 groupby,如果key函数为 None,则只有相同的元素才能放在一组。
import itertools
for key, group in itertools.groupby('AaaBBbcCAAa', lambda c: c.upper()):
print(list(group))
[Running] python -u "e:\pythonee\code\test.py"
['A', 'a', 'a']
['B', 'B', 'b']
['c', 'C']
['A', 'A', 'a']
accumulate(iterable [,func]) 可以计算出一个迭代器,这个迭代器是由特定的二元函数的累计结果生成的,如果不指定的话,默认函数为求和函数。
import itertools
for i in itertools.accumulate([0,1,0,1,1,2,3,5]):
print(i)
[Running] python -u "e:\pythonee\code\test.py"
0
1
1
2
3
5
8
13
logging
在Python应用程序中,日志处理是一项至关重要的任务,它有助于跟踪应用程序的状态、诊断问题以及记录关键信息。Python提供了内置的Logging模块,使得日志记录变得简单而强大。在本文中,我们将探索Logging模块的高级用法,包括日志级别、格式化、处理程序等方面的功能。
1. 日志级别
Logging模块支持多个日志级别,从最低的DEBUG到最高的CRITICAL。使用不同的级别可以控制日志信息的输出粒度,以及对应用程序的影响程度。以下是Python Logging模块支持的日志级别:
- DEBUG:用于详细的调试信息。
- INFO:用于确认应用程序的正常运行。
- WARNING:用于指示潜在的问题,但不影响应用程序的正常工作。
- ERROR:用于指示应用程序中的错误,可能影响部分功能的正常运行。
- CRITICAL:用于指示严重错误,可能导致应用程序崩溃。
import logging
logging.basicConfig(level=logging.DEBUG)
logging.debug('这是一个DEBUG级别的日志信息')
logging.info('这是一个INFO级别的日志信息')
logging.warning('这是一个WARNING级别的日志信息')
logging.error('这是一个ERROR级别的日志信息')
logging.critical('这是一个CRITICAL级别的日志信息')
2. 格式化
Logging模块允许开发者对日志信息进行格式化,以便更好地理解和分析日志内容。可以在日志处理器中指定格式化字符串,其中可以包含特定的占位符,如日志级别、时间戳、模块名等。
import logging
logging.basicConfig(format='%(asctime)s - %(levelname)s - %(message)s', level=logging.DEBUG)
logging.debug('这是一个DEBUG级别的日志信息')
3. 处理程序
Logging模块支持将日志信息发送到不同的处理程序,例如文件、控制台、网络等。通过添加不同的处理程序,可以根据需要将日志信息发送到不同的目的地。
import logging
# 创建一个FileHandler处理程序,将日志信息写入文件
file_handler = logging.FileHandler('app.log')
file_handler.setLevel(logging.ERROR)
# 创建一个StreamHandler处理程序,将日志信息输出到控制台
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.DEBUG)
# 创建一个格式化字符串
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
# 将格式化字符串应用到处理程序
file_handler.setFormatter(formatter)
console_handler.setFormatter(formatter)
# 添加处理程序到Logger对象
logger = logging.getLogger(__name__)
logger.addHandler(file_handler)
logger.addHandler(console_handler)
# 发送日志信息
logger.debug('这是一个DEBUG级别的日志信息')
logger.error('这是一个ERROR级别的日志信息')
4. 过滤器
Logging模块还提供了过滤器的功能,可以根据需求对日志信息进行筛选和过滤。过滤器可以基于日志级别、模块名等条件来过滤日志信息,使得日志记录更加精确和有效。
import logging
class DebugFilter(logging.Filter):
def filter(self, record):
return record.levelno == logging.DEBUG
# 创建一个Logger对象
logger = logging.getLogger(__name__)
# 创建一个FileHandler处理程序
file_handler = logging.FileHandler('debug.log')
file_handler.setLevel(logging.DEBUG)
# 创建一个格式化字符串
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
# 创建一个DebugFilter过滤器
debug_filter = DebugFilter()
# 将格式化字符串应用到处理程序
file_handler.setFormatter(formatter)
# 将过滤器应用到处理程序
file_handler.addFilter(debug_filter)
# 添加处理程序到Logger对象
logger.addHandler(file_handler)
# 发送日志信息
logger.debug('这是一个DEBUG级别的日志信息')
logger.info('这是一个INFO级别的日志信息')
5. 配置文件
Logging模块支持从配置文件中加载配置信息,使得日志处理的配置更加灵活和可配置化。通过配置文件,开发者可以指定日志级别、格式化、处理程序等信息,并根据需要进行调整和修改,而不需要修改源代码。
# logging.conf
[loggers]
keys=root
[handlers]
keys=consoleHandler
[formatters]
keys=sampleFormatter
[logger_root]
level=DEBUG
handlers=consoleHandler
[handler_consoleHandler]
class=StreamHandler
level=DEBUG
formatter=sampleFormatter
args=(sys.stdout,)
[formatter_sampleFormatter]
format=%(asctime)s - %(levelname)s - %(message)s
# 使用配置文件进行日志配置
import logging
import logging.config
logging.config.fileConfig('logging.conf')
logger = logging.getLogger(__name__)
logger.debug('这是一个DEBUG级别的日志信息')
logger.info('这是一个INFO级别的日志信息')
6. 日志轮转
日志轮转是一种常见的日志管理技术,它可以在日志文件达到一定大小或者在特定时间间隔后,自动将当前日志文件重命名并创建一个新的日志文件,以避免日志文件过大或者过期。Python的Logging模块提供了相应的轮转处理程序,方便开发者实现日志轮转功能。
import logging
from logging.handlers import RotatingFileHandler
# 创建一个Logger对象
logger = logging.getLogger(__name__)
# 创建一个RotatingFileHandler处理程序
file_handler = RotatingFileHandler('app.log', maxBytes=10000, backupCount=3)
file_handler.setLevel(logging.DEBUG)
# 创建一个格式化字符串
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
# 将格式化字符串应用到处理程序
file_handler.setFormatter(formatter)
# 添加处理程序到Logger对象
logger.addHandler(file_handler)
# 发送日志信息
for i in range(10):
logger.debug(f'这是第{i+1}条DEBUG级别的日志信息')
7. 日志归档
日志归档是另一种常见的日志管理技术,它可以根据时间周期性地将日志文件进行归档,例如按照每天、每周或者每月生成一个新的日志文件。Python的Logging模块也提供了相应的归档处理程序,可以轻松实现日志的自动归档功能。
import logging
from logging.handlers import TimedRotatingFileHandler
# 创建一个Logger对象
logger = logging.getLogger(__name__)
# 创建一个TimedRotatingFileHandler处理程序
file_handler = TimedRotatingFileHandler('app.log', when='midnight', interval=1, backupCount=3)
file_handler.setLevel(logging.DEBUG)
# 创建一个格式化字符串
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
# 将格式化字符串应用到处理程序
file_handler.setFormatter(formatter)
# 添加处理程序到Logger对象
logger.addHandler(file_handler)
# 发送日志信息
for i in range(10):
logger.debug(f'这是第{i+1}条DEBUG级别的日志信息')
8. 自定义处理程序
除了使用Logging模块提供的内置处理程序外,开发者还可以自定义处理程序来满足特定的需求。通过自定义处理程序,可以将日志信息发送到自定义的目的地,例如数据库、消息队列等,以满足特定场景下的日志记录需求。
import logging
class CustomHandler(logging.Handler):
def emit(self, record):
# 自定义处理逻辑
log_entry = self.format(record)
# 将日志信息发送到自定义目的地
print(f"CustomHandler: {log_entry}")
# 创建一个Logger对象
logger = logging.getLogger(__name__)
# 创建一个自定义处理程序
custom_handler = CustomHandler()
custom_handler.setLevel(logging.DEBUG)
# 创建一个格式化字符串
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
# 将格式化字符串应用到处理程序
custom_handler.setFormatter(formatter)
# 添加处理程序到Logger对象
logger.addHandler(custom_handler)
# 发送日志信息
logger.debug('这是一个DEBUG级别的日志信息')
9. 异常处理中的日志记录
在异常处理中记录日志是一种常见的做法,它可以帮助开发者及时捕获和排查应用程序中的异常情况,从而提升应用程序的稳定性和可靠性。
import logging
# 创建一个Logger对象
logger = logging.getLogger(__name__)
try:
# 尝试执行可能会抛出异常的代码
result = 10 / 0
except Exception as e:
# 在异常处理中记录日志
logger.exception('发生了一个异常')
10. 日志记录最佳实践
在使用Logging模块记录日志时,除了掌握各种高级功能外,还有一些最佳实践值得我们注意。这些最佳实践可以帮助我们更好地利用Logging模块,提高日志记录的效率和可维护性。
- 适当选择日志级别: 使用适当的日志级别可以控制日志信息的输出量,避免在生产环境中输出过多的调试信息。
- 格式化一致性: 保持日志格式的一致性可以方便日志信息的查阅和分析,建议在整个应用程序中采用相同的日志格式。
- 异常处理中的日志记录: 在异常处理中记录日志可以帮助我们及时捕获和排查异常情况,建议在应用程序的关键异常处理逻辑中添加相应的日志记录。
- 日志轮转和归档: 对于日志文件的管理,建议使用日志轮转和归档功能,以避免日志文件过大或者过期。
- 自定义处理程序: 根据实际需求,可以考虑自定义处理程序,将日志信息发送到自定义的目的地,以满足特定场景下的日志记录需求。
11. 日志记录的性能考量
除了功能和最佳实践外,我们还需要考虑日志记录对应用程序性能的影响。尽管Logging模块提供了强大的功能,但不合理的日志记录方式可能会导致性能下降,特别是在高负载的生产环境中。
- 慎用DEBUG级别: 在生产环境中,避免过度使用DEBUG级别的日志记录,因为它们会产生大量的输出,并可能影响应用程序的性能。建议在生产环境中仅记录必要的信息。
- 异步日志记录: 对于高并发的应用程序,考虑使用异步日志记录器来提高性能。异步记录器可以将日志信息缓冲并异步地写入到目标处理程序,从而减少对主线程的阻塞。
- 日志记录器的层级结构: 合理构建日志记录器的层级结构可以帮助我们更好地管理和控制日志记录的性能。尽量避免在每个模块中都创建一个独立的日志记录器,而是应该根据业务逻辑和模块功能来组织日志记录器的层级结构。
12. 性能优化技巧
为了进一步提高日志记录的性能,我们可以采取一些优化技巧来减少日志记录对应用程序性能的影响。
- 批量处理: 将多条日志信息合并成一条进行批量处理,可以减少IO操作和资源消耗,提高日志记录的效率。
- 使用更快的处理程序: 对于性能敏感的场景,可以考虑使用更快速的处理程序,如MemoryHandler或QueueHandler,以减少日志记录对应用程序性能的影响。
- 禁用不必要的处理程序: 定期审查和禁用不必要的处理程序,可以减少日志记录的开销,提高应用程序的性能。
总结
Python中的Logging模块提供了丰富的功能和灵活的配置选项,使得日志记录变得简单而强大。通过本文的介绍,我们深入探讨了Logging模块的高级用法,包括日志级别、格式化、处理程序、过滤器、配置文件等方面的功能。我们还分享了一些日志记录的最佳实践,如适当选择日志级别、保持日志格式的一致性、在异常处理中记录日志等。此外,我们还探讨了日志记录对应用程序性能的影响以及性能优化技巧,帮助开发者更好地管理和维护应用程序的日志信息,提高应用程序的质量和性能。
总的来说,合理地使用Logging模块提供的功能和最佳实践,以及考虑日志记录对应用程序性能的影响并采取相应的性能优化措施,将有助于提高应用程序的可靠性、可维护性和性能,为应用程序的稳定运行和问题排查提供更好的支持。
labs
lab-1
url:https://ssr1.scrape.center/
tasks:spider the films names and details;save into json
goals: to familiar with re,requests,multiprocess
- Step0: 从列表页入手,观察列表页的结构和翻页规则,102条数据,11页,page格式:url/page/{pagenumber} ;detail格式:url/detail/{id}
- Step1:浏览器查看源码:发现电影标题都在<h2>标签中
- Step2: 发现规律:完成列表页的爬取
思路
- 遍历所有页码,构造10页的索引页URL
- 从每个索引页,分析提取每个电脑的详情页URL
实现
import requests
import logging #输出信息,错误日志,便于debug的一个库
import re
from urllib.parse import urljoin
logging.basicConfig(level=logging.INFO,format='%(asctime)s-%(levelname)s:%(message)s')
#定义日志输出级别以及格式
BASE_URL="https://ssr1.scrape.center" #以此为基URL进行拓展目标url
TOTAL_PAGE=10
#定义一个爬取网页源码的函数
def scrape_page(url):
logging.info("Scraping%s...",url)
#错误处理逻辑:1.首先判断状态码是否为200,如果不是输出错误日志信息 2.此外,如果request请求出现异常,输出错误日志
try:
response = requests.get(url)
if response.status_code == 200:
return response.text
logging.error('get invalid status code %s while scraping %s',response.status_code,url)
except requests.RequestException:
logging.error('error occurred while scraping %s',url,exc_info=True)#exc_info打印Traceback的错误堆栈信息
#定义一个解析列表页的函数
def parse_index(html):
pattern=re.compile('<a.*?href="(.*?)".*?class="name">')#提取href属性的正则表达式
items=re.findall(pattern,html)
if not items:
return []
for item in items:
detail_url=urljoin(BASE_URL,item)
logging.info('get detail url %s',detail_url)
yield detail_url
#调用yield返回完整url
#定义一个列表页的爬取方式
def scrape_index(page):
index_url=f'{BASE_URL}/page/{page}'
return scrape_page(index_url)
#定义一个详情页的爬取方式
def scrape_detail(url):
return scrape_page(url)
#到此步已经获取了所有详情网页的url,现在就该爬取url内容
def parse_detail(html):
#匹配图片
cover_pattern=re.compile('class="item.*?<img.*?src="(.*?)".*?class="cover">',re.S)
#匹配名称
name_pattern=re.compile('<h2.*?>(.*?)</h2>')
#匹配类型
categories_pattern=re.compile('<button.*?category<span>(.*?)</span>',re.S)
#匹配上映时间
published_at_pattern=re.compile('(\d{4}-\d{2}-\d{2}\s?上映)',re.S)
#匹配剧情简介
drama_pattern=re.compile('<div.*?drama.*?>.*?<p.*?>(.*?)</p>',re.S)
#匹配评分
score_pattern=re.compile('<p.*?score.*?>(.*?)</p>',re.S)
cover=re.search(cover_pattern,html).group(1).strip() if re.search(cover_pattern,html) else None
name=re.search(name_pattern,html).group(1).strip() if re.search(name_pattern,html) else None
categories=re.findall(categories_pattern,html)if re.search(categories_pattern,html) else []
published_at=re.search(published_at_pattern,html).group(1).strip() if re.search(published_at_pattern,html) else None
drama=re.search(drama_pattern,html).group(1).strip() if re.search(drama_pattern,html) else None
score=float(re.search(score_pattern,html).group(1).strip()) if re.search(score_pattern,html) else None
return {
'cover':cover,
'name':name,
'categories':categories,
'published_at':published_at,
'drama':drama,
'score':score
}
#定义
#main
#此步已经提取了每部电影的基本信息
#最后一步就是保存数据
import json
from os import makedirs
from os.path import exists
RESULT_DIR='/results'
exists(RESULT_DIR) or makedirs(RESULT_DIR)
def save_data(data):
name=data.get('name')
data_path=f'{RESULT_DIR}/{name}.json'
json.dump(data,open(data_path,"w",encoding='utf8'),ensure_ascii=False,indent=2)#indent=2代表json数据格式缩进2格,更美观,ensure_ascii:可以显示中文
for page in range(1,TOTAL_PAGE+1):
index_html=scrape_index(page)
detail_urls=parse_index(index_html)#获取生成器对象detail_url
for detai_url in detail_urls:
detai_url_html=scrape_detail(detai_url)
data=parse_detail(detai_url_html)
logging.info('get detail data%s',data)
logging.info('saveing data to json file')
save_data(data)
logging.info('data saved successfully')
- 多线程加速
改写main函数即可
import multiporcessing
def main(page):
index_html=scrape_index(page)
detail_urls=parse_index(index_html)#获取生成器对象detail_url
for detai_url in detail_urls:
detai_url_html=scrape_detail(detai_url)
data=parse_detail(detai_url_html)
logging.info('get detail data%s',data)
logging.info('saveing data to json file')
save_data(data)
logging.info('data saved successfully')
if __name__=='__main__':
pool=multiprocessing.Pool()#声明一个进程池
pages=range(1,TOTAL_PAGE+1)
pool.map(main,pages)#分别把这10个页码传给这10个函数,进程池根据当前运行环境决定运行多少个进程
pool.close()
pool.join()