犇犇犇犇
2020-08-13 00:09:34
有没有一个时刻,
看着页面上大量精美的动漫图,
却一张张下载点到手麻?
有没有一个时刻,
从网上整理寻找资料,
却看着长长的页面加载进度条感到头大?
有没有一个时刻,
玩着洛谷冬日画板,
看着别人画着一张张图片,
自己只能一个一个,从这点到那?
面对如此繁琐重复的工作
不妨把他交给电脑来完成吧 !
今天,
就让我们来写一只爬虫,
替我们在虚拟的网络上爬呀爬呀爬。
没有人发现每段段尾是押韵的吗
-2:什么是爬虫
-1:python or c++
0:前置知识:关于python
1:第一个任务--下载网页
2:下载一张图片吧
3:第二个任务--有道翻译
列表,元组和字典分不清楚?
判断字符串,列表,元组和字典的一种可能有用的方法:
看到""
是字符串,[]
是列表, ()
是元组, {}
是字典
上面简述了下python基本语法,下面我们就开始吧。
=-=-=-=-=-=-=-=-=-=-(我是分割线)-=-=-=-=-=-=-=-=-=-=
这段代码在文章开头出现过了。代码不长,先贴上来了吧。
import requests # 引入requsets库
response = requests.get('https://www.baidu.com/') # 不带参数的get请求
response.encoding = 'utf-8' # 用utf-8解码
print(response.text) # 输出
我们发现python成功的输出了网页源代码。
但是我们发现这段代码貌似并不能下载所有网页。比如下载洛谷和知乎貌似直接404了?这个是怎么回事呢?这点在后文会讲到。
requests.get()会返回一个response类 即为requests.models.Response
类
这里介绍下response类一些最基本的用法
代码 | 说明 |
---|---|
response.status_code | HTTP请求的返回状态 |
response.content | HTTP响应内容的二进制形式 |
response.text | HTTP响应内容的字符串形式 |
response.apparent_encoding | 从内容中分析出的响应内容编码方式(备选编码方式) |
response.encoding | 从HTTP header中猜测的响应内容编码方式 |
那么这些到底是什么意思呢
我们可以执行一下下面的代码
import requests
response = requests.get('https://www.baidu.com/')
print(response.status_code)
print(response.content)
print(response.text)
print(response.apparent_encoding)
print(response.encoding)
结果如下:
分析下内容:
status_code表示请求状态。200则表示请求成功。如果status_code是404或者502的话就表示请求失败了。
content返回的是响应内容的二进制形式,我们可以发现开头有一个b字母,同时还有一些类似\xe7\x99的乱码。这是字节字符串的标志。
text大部分与content一样,这两个的区别是text用猜测的编码方式将content内容编码成字符串。如果页面是纯ascii码,这那么content与text的结果是一样的,对于其他的文字(比如中文),需要编码才能正常显示。否则就会出现乱码。当然我们也可以使用response.content.decode('utf-8')来手动解码。
我们输出一下content和text的类型,可以发现他们是不同的类型。一个是bytes(字节字符串)类型,另一个是str(字符串)类型。text是现成的字符串,可以当成字符串直接使用;content还要编码。但是text是根据猜测的响应内容编码方式进行解码 (下文的response.encoding)。有的时候系统会判断失误,这时候我们需要手动输入解码方式来进行解码。
response.encoding从HTTP header中猜测的响应内容编码方式。python会从header中的charset提取的编码方式(如下图所示),若header中没有charset字段则会默认为ISO-8859-1,这也是上文说的系统判断失误,无法正确解码的原因。这时候我们需要手动输入解码方式解码。
apparent_encoding会从网页的内容中分析网页编码的方式,所以apparent_encoding比encoding更加准确。python会根据encoding中存的内容进行解码。所以可以采用 response.encoding=response.apparent_encoding
。 当然手动输入解码方式是最靠谱的。
既然python能下载网页,那么python能不能下载图片呢?其实原理是一样的,把图片网页下载下来,然后把它写入文件就可以了。当然,这里直接用字节(byte)的方式写入,所以要用content。
这里给一个随机图片网址 https://api.ixiaowai.cn/api/api.php
打开就会自动随机跳转动漫图。
先放上本人的代码:
import requests
response = requests.get('https://api.ixiaowai.cn/api/api.php') #下载图片
with open("1.jpg","wb+") as f: # 因为这里是以字节的形式写入,所以写入模式要用wb+。如果用w只能写入字符串(str)
f.write(response.content)
我们发现python同目录下出现了一个1.jpg的文件,就是我们要下载的图片了。
注:这里的 1.jpg 是相对路径,可以理解为同目录下创建文件,这里也可以直接改成绝对路径比如 with open("D:\\qwq\\1.jpg","wb+") as f:
这就是在D盘qwq文件夹下写文件。由于\
(反斜杠)是转义字符,需要用\
把\
这个符号给转义一下,这里和c++的printf是一样的。python也可以在"前加r,把它变成原始字符串,就不需要两个\了。
什么,你说要批量下载?加一个循环不就好了吗
import requests
for i in range(10): # 这里range(10)可以理解为从0循环到9,下载10张图。这个数字可以随便改
response = requests.get('https://api.ixiaowai.cn/api/api.php') # 每次下载图片
with open(str(i)+".jpg","wb+") as f: # str(i)+'.jpg'是字符串拼接。每次写入一个新的文件。
f.write(response.content)
这里再放一个去重复图片版本的,可能写的比较丑qwq
输入图片数量 n 即可开始下载,支持读取本地图片并去除重复图片qwq
import requests
import os
import time
s = {}
print("请输入下载数量:")
n = int(input())
cnt = 0
for i in range(1,n+1):
if os.path.exists(str(i)+".jpg"): # 已经存在本地图片
with open(str(i)+".jpg","rb+") as f:
t = f.read()
s[t]=1 # 记录
print(str(i)+'.jpg finish')
continue
response = requests.get('https://api.ixiaowai.cn/api/api.php')
cnt = cnt + 1
if cnt % 100 == 0: # 达到已经数量后停止一段时间
print(str(cnt)+" pictures have been downloaded , waiting...")
time.sleep(30)
num = 0
while response.content in s: # 下载到了重复图片
response = requests.get('https://api.ixiaowai.cn/api/api.php')
cnt = cnt + 1
if cnt % 100 == 0:
print(str(cnt)+" pictures have been downloaded , waiting...")
time.sleep(30)
num = num + 1
print("repeat * "+str(num))
s[response.content]=1
with open(str(i)+".jpg","wb+") as f:
f.write(response.content)
print(str(i)+'.jpg finish')
是不是很简单qaq
桥豆麻袋,之前不是才刚下载了网页吗。怎么突然开始这么复杂了?
翻译吗?我们需要先在左边输入翻译的内容,然后点翻译,然后再右边把内容复制下来。这个python能弄吗?
嗯。没错。对于在前端的我们操作步骤确实是这样。可是对于浏览器和处理这些信息的服务器来说,也是这样操作的吗?在我们点下那个神奇的翻译键的时候,浏览器到底干了什么?
先来讲一个故事。从前有座山,山上有座庙,庙里有个老和尚在给小和尚讲故事。 庙里有一个老和尚,十分有钱,而且特别喜欢收藏各种藏品。于是每周,一个商人便会敲开和尚家的门,与和尚做交易。商人背着各种各样的藏品来到寺庙,老和尚选走自己喜欢的藏品,把钱交给商人。商人带着钱高兴地回去了。
于此同时,在寺庙旁边住着一个强盗。他看到老和尚有那么多钱,于是想冒充商人,抢劫老和尚。算准了商人应该会来的时间,在那天他敲了老和尚的门。可是老和尚也不傻啊,每次商人来之前,他都会听到马蹄声。但是这次却很奇怪。老和尚想了想,越想越感觉不对。最终还是没有开门。
故事纯属我瞎编的,本人文笔不好,体谅一下
这个故事看似没什么关系,但是类比一下,我们可以发现:那个商人就是我们的浏览器,老和尚就是服务器。浏览器带着我们发送的请求,把请求交给服务器,而服务器把结果重新交还给浏览器,浏览器把请求结果带回来,交给我们。
而这个强盗就是我们的python。因为大量的爬虫访问会让服务器压力很大。所以服务器自然不欢迎大量的爬虫访问。所以有些服务器一判断出这个是python的访问,直接把我们拒之门外了。这也是上文的爬虫无法正常访问洛谷或者知乎的原因。
那么这就真的能阻止爬虫访问吗?
我们可以发现上面那个故事中,老和尚通过马蹄声来辨别商人与强盗。那么python伪装成正常浏览器访问不就行了?
这里插播一下网址url的组成,可以大概了解一下下文各种链接的组成。看不懂可以暂时跳过
我们在日常上网中可以看到各式各样的网址,比如https://www.baidu.com/
,https://www.luogu.com.cn/user/35998
,https://www.luogu.com.cn/discuss/lists?forumname=service
这种网址虽然看上去完全不同,甚至还有的带有?以及:
但是其实所有url都是下面这种基本形式组成的
分个段
https://www.luogu.com.cn/record/list?user=uid
我们必须给变量user一个值uid,服务器把内容再传回来 https://baike.baidu.com/item/C%2B%2B#学习指南
好啦,那么具体怎么操作呢?我们进入正题:
这里用谷歌浏览器来示例一下。其他浏览器操作基本相同
主要操作方式依次为右键->检查元素->网络(Network)
这些浏览器拦截的通信内容。这里 Request Method 就是请求方式。我们发现这里有 get 和 post 两种。简单点说,get 就是我们从服务器获得数据。post 就是指我们向服务器提交数据。虽然在实际过程中 get 也可以用来提交数据。
我们在翻译的时候当我们点开翻译按钮的时候,明显我们向服务器提交了我们要翻译的内容,所以我们应该选择 post 。
下面我们来分析一下刚才所有我们看到的这些内容
Request URL是处理我们请求的真实地址
Request Method主要有get和post两种,上面说过了
Status Code 这里200表示请求成功。如果404就是网页找不到了。
Remote Address服务器ip地址和端口号
下面Request Headers是客户端发送请求的headers。服务器通过这个headers来判断是否是非人类的访问。一般通过 User-Agent 这一项来识别是浏览器访问还是代码访问。
这个User-Agent包含我们的系统,浏览器版本号。如果我们用python,那么这个User-Agent就是python+版本号,那么服务器就很容易识别出来把我们屏蔽。当然这个User-Agent可以也可以用python自定义。
下面From Data 是表单数据,post提交的内容。通过这些冒号我们可以发现其实这是一个 字典 。i这一项对应的是我们翻译的内容。
requests自定义headers和提交数据也很简单,在post的时候添加就行了
response = requests.post(url=url,data=data,headers=head)
这里的data和head是 字典 类型。
下面我们开始写代码吧。
import requests
url = "http://fanyi.youdao.com/translate?smartresult=dict&smartresult=rule" # 由于有道翻译feature,translate后面的_o要去掉
data = {
"from":"AUTO",
"to":"AUTO",
"smartresult":"dict",
"client":"fanyideskweb",
"salt":"15801391750396",
"sign":"74bbb50b1bd6c62fbff24be5f3787e2f",
"ts":"1580139175039",
"bv":"e2a78ed30c66e16a857c5b6486a1d326",
"doctype":"json",
"version":"2.1",
"keyfrom":"fanyi.web",
"action":"FY_BY_CLICKBUTTION",
"i":"Hello world!"
} # data就是上面的From Data,这里我们把他写成字典形式
head = {"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36"} # 把浏览器的User-Agent贴进来,这里head也是一个字典
response = requests.post(url=url,data=data,headers=head) # requests标准形式
print(response.text)
运行结果如下。
输出了一个字符串。我们发现"tgt"后面就是我们需要的内容了。我们把这个中文提取出来就好。
当然,我们可以根据一般处理字符串的方法去处理,但是这种方法不方便而且不美观。仔细观察一下,熟悉python数据结构的可以发现,当中的"{}","[]",":"提示我们,这不就是个 字典 吗。其实这是一个json格式,包含的是python可以识别的正常数据结构。
对于这种字符串就是数据结构的情况,python提供一个json库。用法很简单,可以使用json.loads(str)
(str为字符串),也可以直接使用response.json()
。
import requests
import json
url = "http://fanyi.youdao.com/translate?smartresult=dict&smartresult=rule"
data = {
"from":"AUTO",
"to":"AUTO",
"smartresult":"dict",
"client":"fanyideskweb",
"salt":"15801391750396",
"sign":"74bbb50b1bd6c62fbff24be5f3787e2f",
"ts":"1580139175039",
"bv":"e2a78ed30c66e16a857c5b6486a1d326",
"doctype":"json",
"version":"2.1",
"keyfrom":"fanyi.web",
"action":"FY_BY_CLICKBUTTION",
"i":"Hello world!"
}
head = {"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36"}
response = requests.post(url=url,data=data,headers=head)
name = response.json()
print(name)
现在name就是一个字典了。
name是一个字典,其中包含我们需要内容的是translateResult这一项,而这一项里面又包含两个空列表,两个空列表里面又套着一个字典,这个字典里面的tgt是我们需要的结果。可以结合一下上面这张图来理解。禁止套娃
下面给出完整代码:
import requests
import json
url = "http://fanyi.youdao.com/translate?smartresult=dict&smartresult=rule"
data = {
"from":"AUTO",
"to":"AUTO",
"smartresult":"dict",
"client":"fanyideskweb",
"salt":"15801391750396",
"sign":"74bbb50b1bd6c62fbff24be5f3787e2f",
"ts":"1580139175039",
"bv":"e2a78ed30c66e16a857c5b6486a1d326",
"doctype":"json",
"version":"2.1",
"keyfrom":"fanyi.web",
"action":"FY_BY_CLICKBUTTION",
}
data['i']=input() # 输入中文
head = {"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36"}
response = requests.post(url=url,data=data,headers=head)
name = response.json()
print(name['translateResult'][0][0]['tgt'])
我们用python成功地实现了翻译。
之前说我们爬虫可能无法正常访问洛谷,这里一样只要加上User-Agent就可以正常使用了
import requests
url=input()
head = {"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36"}
response = requests.get(url=url,headers=head)
response.encoding = 'utf-8'
print(response.text)
\u4f60\u597d\uff0c\u4e16\u754c
这里\u是干什么用的?
\xe4\xbd\xa0\xe5\xa5\xbd
这里\x又是什么?
有时候我们把python爬虫抓取下来的资源保存再记事本中,想用熟悉的c++进行进一步处理。但是为什么我的c++无法读取?
使用python的过程中,经常有可能会出现一些神奇的错误。明明代码看着完全没问题,甚至在换到一些在线IDE上就能跑出来,可是为什么我的python就一直报错呢?
这其实就是编码问题。这是初学 Python 的容易出现的一个问题之一。
使用python3和高版本的python可以大概率避免这些问题,因为 python2 会默认使用 ASCII 编码,python3 会默认使用 utf-8 编码。
那么ASCII,utf-8,GB2312,unicode。这些到底是什么东西,有什么区别呢?
这还要从编码和计算机的发展说起。
最早的编码是ASCII编码。ASCII码我们都很熟悉,它对应了英语字符与二进制位。ASCII使用一个字节,一个字节有8个二进制位。在英语中,128个符号(7个二进制位)就可以满足,所以一直将1个字节的最高位(第8位)闲置(默认为0),其他7位用于编码。后来才扩展了最高位,共可以表示256个符号。在表示英语,ASCII码绰绰有余。
但是世界上并不是只有英语一种语言,对于其他语言,比如汉字,明显256位根本不够。于是每个国家开始自己定自家的编码。这种表示方法简体中文叫做GB2312, 繁体中文叫Big5,日文叫Shift_JIS。这种编码统称为ANSI。ANSI只是一种代称,在英文操作系统中 ANSI 编码代表 ASCII,在简体中文操作系统 ANSI 编码代表 GB2312,在繁体中文操作系统相当于Big5 。
虽然这种方法的确解决了其他语言编码的问题,但是缺点也很明显。不同 ANSI 编码之间互不兼容,所以我们无法将两种语言同时保存在同一个 ANSI 编码的文本中。这种编码只有当前操作语言操作系统有效,而且与unicode , utf-8编码无关。汉字用两个字节表示一个字符。理论上最多可以表示 256 x 256 = 65536 个符号。GB2312是中国自己制定的编码,后来加入繁体发展成了GBK,最后加入日语和韩语成为GB18030。ANSI可以认为是ASCII的一种扩充,所以ANSI码前127个与ASCII码相同。
但是这种编码明显有局限性,不利于全球公用。所以迫切需要一种能表达全球所有语言的编码。于是Unicode诞生了,Unicode又称为"万国码",,它为每种语言中的每个字符设定了统一并且唯一的二进制编码,以满足跨语言、跨平台进行文本转换、处理的要求。因为Python的诞生比Unicode标准发布的时间早,所以Python2只支持ASCII编码,处理Unicode就会出现问题。也就是我们看到的乱码问题。
Unicode 是一个很大的字符集,所有的Unicode可以到 这里 查看。Unicode 只是一个符号集,给每一个字符一个ID,比如汉字'我'的ID(码位)就是25105,记作U+6211(25105的十六进制为0x6211)但是并没有规定怎么将码位转换为字节序列来给计算机储存。Unicode只给了每一个汉字一个ID,比如给了'我'这个字一个ID 25105 , 但是并没有规定怎么把这个25105给转化成二进制储存到计算机里。Unicode 与 UTF-8 的区别就是 UTF-8 是一种编码规则,也就是 Unicode 的一种实现方式。Unicode 还包含 UTF-16、UTF-32 等编码。UTF-8、UTF-16 等等这些编码规则负责将这些 Unicode 的 ID 转成二进制码储存到计算机里。
最开始的 Unicode 实现方式十分暴力。明显,一个字节只能储存256个符号,完全储存不下Unicode如此庞大的字符数。既然一个字节存不下,那就用三个或四个字节来表示一个字符啊。看似可行,但是比如英语字母只需要1个字节即可表示,为了填充每个字符4个字节的位置,前3个字节必然都是0,只有最后一个字节是有效内容。这会使纯英语或其他语言的文本文件的大小多出好多倍,对于存储来说是极大的浪费。
随着互联网的出现,对统一的编码方式需求越来越大,这使得 Unicode 开始推广,同时也产生了一种 UTF-8 的编码方式。UTF-8 就是在互联网上使用最广的一种 Unicode 的实现方式,很多网页的源码上会有类似 charset="UTF-8" 的信息,表示该网页正是用的UTF-8编码。
UTF-8 最大的一个特点,就是它是一种变长的编码方式。它可以使用1~4 个字节表示一个符号。
UTF-8 的编码规则:(好像和本文的主题没啥关系啊,如果感兴趣的可以看一下qaq)
1)对于单字节的符号,字节的第一位设为0,后面7位为这个符号的 Unicode 码。因此对于英语字母,UTF-8 编码和 ASCII 码是相同的。
2)对于n字节的符号(n > 1),第一个字节的前n位都设为1,第n + 1位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的 Unicode 码。
如下表所示,x表示可用编码的位。
Unicode符号范围(十六进制) | UTF-8编码方式(二进制) |
---|---|
0-7F | 0xxxxxxx |
80-7FF | 110xxxxx 10xxxxxx |
800-FFFF | 1110xxxx 10xxxxxx 10xxxxxx |
10000-10FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
简单点说,对于 UTF-8编码 开头有多少个连续的1,这个字符就占多少个字节。
举个例子,我们来讲一下怎么把汉字'我'转化成UTF-8编码值
首先我们先获取'我'的Unicode值,输入ord('我')
>>> ord('我')
25105
得到25105,我们把25105转为十六进制,使用hex函数
>>> hex(25105)
'0x6211'
25105的16进制为6211,在范围800-FFFF之间,所以用3个字节表示。
现在我们已经确定了'我'的形式应该为1110xxxx 10xxxxxx 10xxxxxx,所以下一步我们只要把这些x给填上就可以了。这些x形式位4+6+6的形式,一共要填16位。
我们把25105转为二进制。输入bin(25105)
>>> bin(25105)
'0b110001000010001'
得到二进制为110001000010001。可是这里只有15个位,而我们要填16个数,所以在开头补一个0,得到
把他分割成4,6,6的形式
得到
11100110,10001000,10010001这三个数分别转为16进制得到e6 88 91
>>> hex(0b11100110)
'0xe6'
>>> hex(0b10001000)
'0x88'
>>> hex(0b10010001)
'0x91'
这样我们就得到了'我'的UTF-8编码值,测试一下。
>>> b'\xe6\x88\x91'.decode("utf-8")
'我'
实际上大部分汉字都使用3个字节。
处理编码问题最常见的命令是encode与decode。
decode的作用是将二进制数据解码成unicode。
encode的作用是将unicode编码编码成二进制数据。
简单说,decode就是“解密”,而encode就是“加密”。
python对于字符串拼接时候报错,比如string=string1+string2这种类型的,python2要求这两个字符串都是一样的编码方式。比如普通字符串和 Unicode 字符串进行拼接就会报错。这时候需要将普通字符串使用string.decode('utf-8')来转成一样的类型。
如果无法显示unicode中文,比如'\u4f60\u597d\uff0c\u4e16\u754c'这时候可以使用decode('unicode_escape')来解码,也可以使用eval函数,在字符串前加上u,告诉编译器这是unicode编码。eval("u"+"\'"+string+"\'")
。
当不同的编码系统进行相互转换的时候,可以利用 Unicode 做一个中介。把其他编码先decode成unicode,再把unicode编码成其他编码,比如GB2312。
文件操作时使用编码操作
f1 = open("test.txt", encoding="")
encoding这里加上文件的编码就行了。
对于python编码,使用python3可以避免大部分问题。
上文说了我们可以通过添加User-Agent来伪装成正常的人类点击。但是,服务器还是有一种暴力的方法来判断是不是人类的访问。记录ip访问量,设定一个阈值。如果访问量超过一个阈值,直接把它掐掉。因为爬虫一般访问速度都远远大于人类。
判断是人类还是程序,大部分网站的处理方法都是使用验证码。这种验证码对于人类没有问题。正常人类可以输入验证码,但是我们的python...就没这么好办了......
为了避免触发阈值,一般有两种做法。
第一种是降速,降低爬虫速度,让它看起来更加像是人类的访问。实现方法很简单,每次访问完成后time.sleep(5)
,等待个几秒钟就好。虽然处理简单,但是缺点很明显,就是访问速度太慢了,工作效率低下。
第二种就是使用ip代理。kkksc03:把你ip扬了。 代理是什么?代理就像一个跑腿的。既然我去的次数太多,被限制了,那我就换一个跑腿的帮我访问。每次我们把需要访问的内容给代理,然后代理访问服务器,再把传回来的结果原封不动地告诉你。就相当于每次换ip访问,那么每个ip访问的速度就很慢了。服务器看到的访问地址是代理ip的地址。
代理ip(proxy)的形式是 IP类型://代理ip:端口号
IP类型主要有http和https。
首先我们要找到代理ip。这里提供一个貌似可用的提供免费代理ip的网站 http://www.xiladaili.com/ 。至少本人测试的时候还是能用的)
建议如果可能尽量使用IP类型为https的代理ip。当然,如果自己有服务器或者其他代理ip当然是最好的选择。
这里再提供一个查看ip的网站 https://www.myip.com/ 。访问就能看到自己的ip。
使用代理也很简单,建一个porxies的字典,在访问的时候加上这个函数就行了。如果是http那么key就是http。如果是https那么key就是https。
http:proxies={"http":"http://代理ip:端口号"}
https:proxies={"https":"https://代理ip:端口号"}
访问时
response = requests.get(url=url,proxies=proxies)
这里就用上面网站的第一个https类型的代理ip来演示
代码:
import requests
url='https://www.myip.com/'
proxies={
"https":"https://184.82.128.211:8080/"
}
head = {"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36"}
response = requests.post(url=url,headers=head,proxies=proxies)
print(response.text)
这里也可以输出一下 response.status_code
查看访问状态,成功的话状态码应为200。如果出错的话可以多换几个ip试一试。
成功访问运行界面如下。
我们发现我们的python成功使用了代理ip。
当然我们也可以交替使用代理ip,避免由于单个ip访问次数过快过高被封。实现方法也很简单,把所有ip放到一个列表里,每次随机使用。这里需要使用 random库。
import requests
import random
url='https://www.myip.com/'
proxies={}
ip = ["https://184.82.128.211:8080/","https://103.60.137.2:22589/","https://89.28.53.42:8080/"] # 保存可用的代理ip
proxies["https"]=random.choice(ip) # 随机使用
head = {"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36"}
response = requests.post(url=url,headers=head,proxies=proxies)
print(response.text)
每到元旦的时候,为了画洛谷画板,某群里便会有着一堆求cookie的人。那么cookie到底是什么?好吃吗?
别说,cookie(曲奇)还是挺好吃的
Cookie,有时也用其复数形式 Cookies。类型为“小型文本文件”,是某些网站为了辨别用户身份,进行Session跟踪而储存在用户本地终端上的数据(通常经过加密),由用户客户端计算机暂时或永久保存的信息。
——《百度百科》
看着很复杂的样子,打个比方,有一天,你和你的npy去酒店度假。你们去酒店做了登记,于是酒店发给了你一张房卡。而这张房卡可以认为就是我们的cookie,它保留在你的手上,你可以凭借这张房卡自由地进出房间,它告诉房间你是这件房间的主人,避免每次回酒店都要重新登记一次(这不废话),同时也防止了无关人员进出你的房间。但是这张卡只有当晚才有效,到了第二天必须要重新付钱登记才可以继续使用,所以有的cookie是暂时的,cookie暂时保留在我们手上,但是过了一定时间需要重新登录。
cookie的应用十分广泛。比如我们登陆了洛谷,这时候我发了请求,告诉服务器我的账户和密码,然后我打开题目开始写题。当我们写完题准备提交的时候,浏览器把代码提交给服务器。那么问题来了,服务器怎么知道这份代码是谁提交的呢?cookie就很好地解决了这个问题,登录洛谷的时候服务端给客户端发送一个cookie,使用的时候客户端发送cookie证明这是”我“
一般来说,cookie的使用流程为
我们可以在浏览器直接查看cookie。这里以谷歌浏览器为例。
首先我们先登录目标网站,然后点击 F12(可能有些笔记本需要同时按fn键) - Application - cookie
然后就能看到cookie了。
python使用过程中,cookie是一个字典,如上图,key为左侧Name , Value为右侧Value。
我们可以把他变成字符串的形式给爬虫使用,不同的项之间用;
连接。形式为"Name=Value;第二项,形式同上..."
比如洛谷的就可以用"_uid=xxx;__client_id=xxx"
这两项来登录
headers={
"cookie":cookies
}
response = requests.post("请求网址",headers=headers)
这里给一个通过洛谷cookie登录洛谷的示例,需要用到re库,可以 cmd 输入 pip install re
安装
import requests
import re
uid = input("_uid=")
client = input("__client_id=")
string = "_uid="+uid+";__client_id="+client # 这里的string为拼接的cookie
headers={
"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36"
,"cookie":string # 使用cookie
}
response = requests.get("https://www.luogu.com.cn",headers=headers)
response.encoding = 'utf-8' # 解码
s = response.text # 获取网页源代码
# 以下程序为正则表达式,在后文会提到,作用为抓取登录洛谷后打卡页面的id
p = re.search(r"<h2 style='margin-bottom: 0'>(.*?)</h2>",s)
if p:
s=p.group()
p = re.search(r"target=\"_blank\">(.*?)</a>",s)
print("成功登录 " + p.group(1))
else:
p = re.search(r"<h2>欢迎回来,(.*?)</h2>",s)
if p:
p = re.search(r"target=\"_blank\">(.*?)</a>",s)
print("成功登录 " + p.group(1))
else:
ref = "https://www.luogu.com.cn/api/user/search?keyword="+uid
response = requests.get(ref,headers=headers)
response.encoding = 'utf-8'
id = response.json()
id = id['users'][0]["name"]
print("登录失败","uid:",uid,"id:",id)
和之前一样,我们来看一下我们点击洛谷画板时我们的浏览器干了什么。
我们先使用黑色去涂画板的左上角,可以发现浏览器发送了一个post,其中x: 0 y: 0 color: 0。我们换成最后一个颜色,点击右下角涂色,我们可以发现发送的post为x: 799 y: 399 color: 31。于是我们可以发现这个画板大小为800*400,其中左上角为坐标原点,颜色按顺序为0~31。当前画板的情况可以在 https://www.luogu.com.cn/paintBoard/board
查看。这上面其实就是一个32进制,对应着每个点颜色。
我们需要把我们的图画保存在board.json里,其中board形式为列表套列表,每个小列表为x,y,col保存每个点的信息。比如[[1,1,0],[1,2,1]]
就是需要在坐标(1,1)涂黑色,在(1,2)涂白色。
我们还需要把cookie提前存在cookie.json里。洛谷只需要__client_id和_uid这两项。我们使用列表套字符串,形式为["_uid=xxx;__client_id=xxx","_uid=xxx;__client_id=xxx"]
,每次画点按顺序使用cookie,使用完一轮以后等待冷却30s。
要注意的就是我们获取画板状态 https://www.luogu.com.cn/paintBoard/board
虽然画板的确实每行只有600个有效字符,但是由于行尾有换行符,所以实际上每行有601个字符。即x,y坐标实际的位置为 x*601+y
下面放一下本蒟蒻的代码。由于每年洛谷画板可能都有微小的变化,不保证每年都能用,上面已经说明了写法,私认为最稳定的做法还是自己写一个qaq
一下程序的board.json,cookie.json和 ouuan 的形式相同,生成的文件可以直接套用qwq
由于本人较菜,当时参考了一下他的写法,这里感谢一下神ouuan
import requests
import json
import time
headers={
"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36"
,"cookie":""
}
with open("cookies.json","r") as load_f:
cookie = json.load(load_f) # 引入cookie
with open("board.json","r") as load_f:
board = json.load(load_f) # 引入图画内容
def paint(x,y,c): # 涂色函数
data={
'x':x,
'y':y,
'color':c,
} # data为我们需要填充的颜色和坐标
print(x,y,c)
global cur
headers["cookie"]=cookie[cur]
print(data,headers)
response = requests.post("http://www.luogu.com.cn/paintBoard/paint",data=data,headers=headers) # 填色
pause=31
mark=0
t0 = time.time()
while 1:
headers["cookie"]=cookie[0]
pboard = requests.get("http://www.luogu.com.cn/paintBoard/board",headers=headers)
d=[]
cnt=1
for point in board:
if cnt>len(cookie):
break
x=point[0]
y=point[1]
c=point[2]
if int(pboard.text[x*601+y],32) != c:
cnt=cnt+1
d.append(point)
cur=0
t = time.time()-t0
# print(t)
if mark == 1:
time.sleep(pause-t)
mark=1
t0 = time.time()
for point in d:
headers["cookie"]=cookie[cur]
x=point[0]
y=point[1]
c=point[2]
paint(x,y,c)
cur=cur+1
当然这里还需要生成一个board.json,这里给出一个把图片生成board的代码。把需要处理的图片重命名为1.jpg,放入python的同目录下。支持把图片压缩成x*y。width,height,startx,starty四个参数需要自定义。
这里需要image库和json库。可以 cmd 输入 pip install image
安装
from PIL import Image
from colorsys import rgb_to_hsv
import json
import math
colors=[(0, 0, 0),(255, 255, 255),(170, 170, 170),(85, 85, 85),(254, 211, 199),(255, 196, 206),(250, 172, 142),(255, 139, 131),(244, 67, 54),(233, 30, 99),(226, 102, 158),(156, 39, 176),(103, 58, 183),(63, 81, 181),(0, 70, 112),(5, 113, 151),(33, 150, 243),(0, 188, 212),(59, 229, 219),(151, 253, 220),(22, 115, 0),(55, 169, 60),(137, 230, 66),(215, 255, 7),(255, 246, 209),(248, 203, 140),(255, 235, 59),(255, 193, 7),(255, 152, 0),(255, 87, 34),(184, 63, 39),(121, 85, 72)]
def dis(x,y):
rmean = (x[0] +y[0])/2
r = x[0] - y[0];
g = x[1] - y[1]
b = x[2] - y[2]
return math.sqrt((((512+rmean)*r*r)/256) + 4*g*g + (((767-rmean)*b*b)/256))
def closest(col):
minn=10000000000
for i in colors:
sum=dis(col,i)
if minn>sum:
minn=sum
ans=colors.index(i)
return ans
width=100 # 图片压缩后的宽度
height=100 # 图片压缩后的高度
startx=10 # 开始画的点的x坐标
starty=10 # 开始画的点的y坐标
f=open('board.json','w')
lena = Image.open("1.jpg")
picture = lena.resize((width, height),Image.ANTIALIAS)
a = picture.load()
d = list()
for i in range(picture.width):
for j in range(picture.height):
d.append( (startx+i,starty+j,closest(a[i,j])) )
string=json.dumps(d)
f.write(string)
给出一个可以根据board.json生成的图片预览
from PIL import Image
import json
colors=[(0, 0, 0),(255, 255, 255),(170, 170, 170),(85, 85, 85),(254, 211, 199),(255, 196, 206),(250, 172, 142),(255, 139, 131),(244, 67, 54),(233, 30, 99),(226, 102, 158),(156, 39, 176),(103, 58, 183),(63, 81, 181),(0, 70, 112),(5, 113, 151),(33, 150, 243),(0, 188, 212),(59, 229, 219),(151, 253, 220),(22, 115, 0),(55, 169, 60),(137, 230, 66),(215, 255, 7),(255, 246, 209),(248, 203, 140),(255, 235, 59),(255, 193, 7),(255, 152, 0),(255, 87, 34),(184, 63, 39),(121, 85, 72)]
with open("board.json","r") as load_f:
board = json.load(load_f)
x=0
y=0
for point in board:
x=max(x,point[0])
y=max(y,point[1])
lena = Image.new("RGB",(x+1,y+1))
for point in board:
x=point[0]
y=point[1]
c=colors[point[2]]
lena.putpixel((x,y),c)
lena.show()
以下程序可以加入cookie,并查看cookie是否有效。
需要在桌面新建一个cookie.json,第一次使用需要在里面写入[]
(一个空的列表),否则会导致读取本地cookie读取失败
import json
import requests
import re
with open("cookies.json","r") as load_f:
l = json.load(load_f)
d = {}
print("输入0保存并退出")
for i in l:
d[i]=1
while 1:
client = input("__client_id=")
if client == "0":
break
uid = input("_uid=")
cookies = {"_uid":uid,"__client_id":client}
str = ""
for i in cookies:
str = str+i+"="+cookies[i]+";"
string = str[:-1]
if string in d:
print("该cookie已经存在,请勿重复添加")
continue
headers={
"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36"
,"cookie":str
}
response = requests.get("https://www.luogu.com.cn",headers=headers)
response.encoding = 'utf-8'
# print(response.text)
s = response.text
p = re.search(r"<h2 style='margin-bottom: 0'>(.*?)</h2>",s)
if p:
s=p.group()
p = re.search(r"target=\"_blank\">(.*?)</a>",s)
l.append(string)
d[string]=1
print("成功添加 " + p.group(1))
else:
p = re.search(r"<h2>欢迎回来,(.*?)</h2>",s)
if p:
# print(p.group())
p = re.search(r"target=\"_blank\">(.*?)</a>",s)
l.append(string)
d[string]=1
print("成功添加 " + p.group(1))
else:
print("添加失败")
print(l)
print(len(l))
string=json.dumps(l)
with open('cookies.json','w') as f:
f.write(string)
再给一个根据cookie.json来登录洛谷,可以解决查看cookie是否失效的问题,需要re库
import json
import requests
import re
with open("cookies.json","r") as load_f:
l = json.load(load_f)
for string in l:
headers={
"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36"
,"cookie":string
}
response = requests.get("https://www.luogu.com.cn",headers=headers)
response.encoding = 'utf-8'
# print(response.text)
s = response.text
p = re.search(r"<h2 style='margin-bottom: 0'>(.*?)</h2>",s)
if p:
s=p.group()
p = re.search(r"target=\"_blank\">(.*?)</a>",s)
print("成功登录 " + p.group(1))
else:
p = re.search(r"<h2>欢迎回来,(.*?)</h2>",s)
if p:
# print(p.group())
p = re.search(r"target=\"_blank\">(.*?)</a>",s)
print("成功登录 " + p.group(1))
else:
p = re.search(r"uid=(.*?);",string)
uid = p.group(1)
ref = "https://www.luogu.com.cn/api/user/search?keyword="+uid
response = requests.get(ref,headers=headers)
response.encoding = 'utf-8'
id = response.json()
id = id['users'][0]["name"]
print("登录失败","uid:",uid,"id:",id)
print(l)
print(len(l))
cookie的使解决了爬虫来"登录"账户的问题。但既然cookie可以用来画洛谷画板,那么有什么事情干不了呢?有了cookie正如同有了你的账户密码,在有效时间内可以干任何事情,cookie是敏感信息,所以还是尽量不要随便把cookie给不值得信任的人。
所以有好心人能元旦的时候给我cookie吗,仅洛谷画板使用,有意向者可以加QQ3473131422
在我们使用requests下载网页的时候,我们自然需要对下载的字符串进行处理,提取需要的信息。这便是一个字符串匹配的问题。
算法竞赛中,有许多处理字符串的算法比如KMP,自动AC机 AC自动机。但是这里不是算法竞赛,我们duck不必写这些字符串算法。python有一个强大的工具叫做正则表达式,她可以替代大篇幅的代码来做字符串匹配。
使用正则表达式需要一个"模式串"和一个"待匹配字符串"
比如我们下载了一个字符串
<div class="am-u-sm-12 lg-small"> <br>你已经在洛谷连续打卡了 <strong>666</strong> 天<br> </div>
我们需要获得打卡的天数,也就是说我们需要匹配满足<strong>xxx</strong>
的字符串,其中 xxx 是我们需要的内容。所以上面整个字符串为待匹配字符串,而我们需要找到满足形式为<strong>xxx</strong>
的字符串,这个字符串为"模式串",也就是正则表达式。简单点说,正则表达式就是一种符合某个模式(规则)的文本。
python正则表达式需要使用re库 import re
常用的re函数有常用的正则表达式函数有re.match(),re.search(),re.findall(),re.compile()。
这类函数的形式都是一样的,拿 re.match 来举例 re.match(pattern,string,flag=0)
patter为正则表达式,可以理解为我们需要找的字符。string为待匹配字符串。flag为标志位,控制匹配的方式,例如匹配的时候是否区分大小写。
re.match():从头开始匹配,如果遇到一个无法匹配的字符,则返回None,否则返回匹配结果。
re.search():匹配整个字符串,返回第一个匹配到的位置,没有返回None。
re.findall():匹配整个字符串,返回一个列表包含所有能匹配的字符串
re.compile():编译正则表达式。把字符串编译成正则表达式对象。可以直接作为pattern使用。
match与search的区别是 match必须从头开始匹配,如果找到一个字符不符合正则表达式,直接返回None。search是匹配整个字符串,匹配到的字符串不一定要从头开始。简单点说,match匹配必须包含第一个字符,而search不一定。
>>> import re # 引入re库
>>> str = "Hello Hello world" # 待匹配字符串
>>> re.match(r"Hello",str) # 从str中从头开始匹配 Hello
<re.Match object; span=(0, 5), match='Hello'> # 找到了,下标从0到4
>>> re.search(r"Hello",str) # 从str中匹配 Hello
<re.Match object; span=(0, 5), match='Hello'> # 找到了,下标从0到4
>>> re.findall(r"Hello",str) # 从str中匹配所有的 Hello
['Hello', 'Hello'] # 返回一个列表,包含所有的匹配内容
>>> re.match(r"world",str) # 从str中从头开始匹配 world。str中第一个字符为H,与world匹配失败,直接返回None
>>> re.search(r"world",str) # 从str中匹配 world
<re.Match object; span=(12, 17), match='world'> # search不一定要从头开始,匹配下标为12-16
>>> p = re.search(r"world",str) # 把匹配的内容赋值给p
>>> p.group(0) # 使用group(0)返回匹配的字符串内容
'world'
>>> p.span() # 使用span返回匹配的字符串下标
(12, 17)
>>> pattern=re.compile("Hello") # 编译成正则表达式
>>> re.match(pattern,str) # 可以直接调用,减少重新编译的时间
<re.Match object; span=(0, 5), match='Hello'>
模式串前加上 r 意思是原始字符串,原始字符串作用是不转义反斜杠(\)。例如 \n 代表换行符,但是在原字符串中就是"\n"这个字符串。在正则表达式中大部分情况下我们不需要用转义,使用原始字符串可以避免许多麻烦。当然在上面这个例子中没有涉及到\
这个字符,所以加不加都是一样的。
python字符串下标从0开始,search与match返回的下标满足左闭右开,即如果返回的坐标为(l,r),则实际位置为(l,r-1)。
但是这样明显是不够的,实际应用中我们不可能只需要匹配类似Hello这种固定的字符串,比如我们需要匹配<strong>xxx</strong>
,其中xxx为未知的字符。这时候我们可以用一些基本符号(元字符)来代替这些X。
这里用一个表格来列举一下常见的一些元字符。
符号 | 说明(可用来代替的字符) | 表达式 | 可匹配的解 | |
---|---|---|---|---|
. | 换行符以外的字符 | a.b | acb,asb,a2b | |
^ | 以...开始 | ^AK | AKIOI,AK123 | |
美元符号,这里会被识别成LaTeX,所以用¥代替 | 以...结束 | AK¥ | JohnVictorAK,321AK | |
\b | 匹配单词边界,不匹配字符。单词边界指单词前后与空格间的位置 | asd\b | 123asd,不能匹配asd1(asd后不为空格) | |
\d | 匹配数字1-9 | ab\dc | ab1c,ab2c,ab9c | |
\D | 匹配非数字 | ab\Dc | abxc,ab&c | |
\s | 匹配空白符(包括空格、制表符、换页符等) | ab\sc | ab c | |
\S | 匹配非空白符 | ab\Sc | abyc,ab | c |
\w | 匹配字母、数字、下划线 | ab\wc | ab_c,ab1c | |
[] | 匹配括号内的任意字符 | a[b,c,d,e]f | abf,acf,adf,aef | |
\ | 转移字符,可以转义以上的元字符变成普通的字符 | a[b\.\\]c | abc,a.c,a\c |
这样对于上面那种情况,<strong>xxx</strong>
中的 x 为数字,可以用\d
匹配。
>>> str = "<div class=\"am-u-sm-12 lg-small\"> <br>你已经在洛谷连续打卡了 <strong>666</strong> 天<br> </div>"
>>> p=re.search(r"<strong>\d\d\d</strong>",str)
>>> p.group(0)
'<strong>666</strong>'
又例如我们需要匹配一个ip地址
我们首先需要找出ip地址的特性。它是由https://地址.地址.地址.地址:端口号 的形式。这里地址和端口号为数字,可以用.或者\d匹配。
>>> str = r"Welcome to visit https://129.226.190.205:443/"
>>> p=re.search(r"https://\d\d\d\.\d\d\d\.\d\d\d\.\d\d\d:\d\d\d/",str) # 这里ip地址中的.由于不是元字符,而是做普通字符用,所以要转义
>>> p
<re.Match object; span=(17, 45), match='https://129.226.190.205:443/'>
这也是我们使用简单正则表达式的一般步骤。
有了元字符,我们可以完成对于大部分字符串的匹配。但是有的时候,我们会遇到字符大量重复,或者是只需要匹配夹在两个特定字符串中间的所有内容,根本不知道中间有多少个字符。
为了解决这些重复的问题,我们可以使用重复限定符,把他放在字符后面,表示字符的重复次数,让表达式看起来更加简洁。
同样的,用一个表格来表示常用的重复限定符
符号 | 说明 | 表达式 | 可匹配的解 |
---|---|---|---|
* | 匹配0到多次 | abc* | ab,abccccccc |
+ | 匹配1到多次 | abc+ | abc,abccccccc |
? | 匹配0或1次 | abc? | ab,abc |
{m} | 匹配m次 | abc{3}de | abcccde |
{m,} | 匹配m或多次(包含m次) | abc{3,}de | abcccde,abcccccde |
{,m} | 匹配0到m次(包含m次) | abc{,3}de | abde,abcccde |
{n,m} | 匹配n到m次(包含n,m次) | abc{2,3}de | abccde,abcccde |
ip地址的匹配可以简化为
https://\d+\.\d+\.\d+\.\d+:\d+/
匹配网页H1标签中的所有内容
<h1>.*?</h1>
贪婪就是字面意思,匹配的越多越好。
比如,我们在aabaabb中找以a为开头,b为结尾的字符串。
贪婪模式匹配了所有的字符aabaabb。但是事实上,满足条件的字符串不止一个,我们只需要前3个字符aab就可以满足要求了。贪婪模式就是在能匹配的情况下越多越好,相反,非贪婪模式就是尽可能地少匹配。
通常,{m,n}
,{m,}
,*
,+
,?
属于贪婪模式(匹配优先量词)
{m,n}?
,{m,}?
,*?
,+?
,??
属于非贪婪模式(忽略优先量词)
>>> str = "aabaabb"
>>> re.search(r'a.*b',str) # *匹配,贪婪模式
<re.Match object; span=(0, 7), match='aabaabb'>
>>> re.search(r'a.*?b',str) # *?匹配,非贪婪模式
<re.Match object; span=(0, 3), match='aab'>
为了满足更加多的匹配需求,我们引入分组与条件。上面我们说了重复限定符。但是上面的重复限定符只能重复前面那个字符。分组就可以让我们对多个字符使用限定符。
我们用()
来分组,分组中的内容可以看作一个整体。
特别如果在findall模式中分组,将返回与分组匹配的文本列表。如果使用了不只一个分组,那么列表中的每项都是一个元组,包含每个分组的文本。
放一段代码理解下qaq
>>> str = '<strong>666</strong>'
>>> re.findall(r'<strong>.*</strong>',str)
['<strong>666</strong>']
>>> re.findall(r'<strong>(.*?)</strong>',str)
['666']
>>>
我们发现,在.*旁边加了括号,findall便会只保留括号的内容。
比如现在'@[犇犇犇犇](/user/35998)'
,我们想要同时匹配出 犇犇犇犇 和 35998。
我们先来转化下形式@[xxx](/user/xxx)
。在我们需要的xxx两旁加上括号,@[(xxx)](/user/(xxx))
,然后把xxx用.*替代,还要注意一下这里的[
与(
都是匹配字符,需要转义。
>>> str = '@[犇犇犇犇](/user/35998)'
>>> re.findall(r'@\[(.*?)\]\(/user/(.*?)\)',str)
[('犇犇犇犇', '35998')]
>>> str = '@[犇犇犇犇](/user/35998) @[JohnVictor](/user/254752)'
>>> re.findall(r'@\[(.*?)\]\(/user/(.*?)\)',str) # 找到多个结果,每个结果为一个元组。
[('犇犇犇犇', '35998'), ('JohnVictor', '254752')]
我们发现如果要匹配多个分组时,findall返回一个列表,每项都是一个元组,元组内为每个分组内容。
我们匹配网址的时候,会遇到http和https共存的情况,那么我们需要满足的条件时http或者https。这就需要条件或。
条件或格式为 X|Y
表示匹配X或Y,从左到右匹配,满足第一个条件就不会继续匹配第二个条件。
我们可以使用 (http|https)://\d+\.\d+\.\d+\.\d+:\d+/
来同时匹配http与https。
既然findall可以返回每个分组内的内容,其实match和search也可以使用group函数来达到同样的效果。
group()与group(0)效果相同,就是匹配到的整个字符串。
group(1) 列出第一个括号内容,group(2) 列出第二个括号内容,group(n) 列出第n个括号内容。
groups() 列出所有括号内容的元组。
>>> str = '@[犇犇犇犇](/user/35998)'
>>> p=re.search(r'@\[(.*?)\]\(/user/(.*?)\)',str)
>>> p.group()
'@[犇犇犇犇](/user/35998)'
>>> p.group(1)
'犇犇犇犇'
>>> p.group(2)
'35998'
>>> p.groups()
('犇犇犇犇', '35998')
之前说match标准形式的时候说到match标准形式为re.match(pattern,string,flag=0)
现在来讲一下最后的那个flag有什么用。
常用的匹配方式如下:
符号 | 说明 |
---|---|
re.I | 忽略大小写 |
re.M | 多行模式,^ 与美元符号会同时从每行进行匹配 |
re.S | . 可匹配任何字符,包括换行符 |
re.X | 冗余模式,忽略正则表达式中的空格与#注释 |
代码示例部分:
re.I 忽略大小写
>>> str = "HELLO WORLD"
>>> re.search(r'hello',str)
>>> re.search(r'hello',str,re.I)
<re.Match object; span=(0, 5), match='HELLO'>
re.M 多行模式,^
与美元符号会同时从每行进行匹配
美元符号就是上文7.2中表格的第三行符号,表示以...结束。这里打美元符号貌似会被洛谷博客渲染成LaTeX,导致后面格式全部挂了qaq
>>> str = "HELLO WORLD\n123%a"
>>> re.findall(r'^\w+',str)
['HELLO']
>>> re.findall(r'^\w+',str,re.M)
['HELLO', '123']
re.S .
可匹配任何字符,包括换行符
>>> str = "HELLO WORLD\n123%a"
>>> re.findall(r'.+',str)
['HELLO WORLD', '123%a']
>>> re.findall(r'.+',str,re.S)
['HELLO WORLD\n123%a']
re.X 冗余模式,忽略正则表达式中的空格与#注释
>>> str = 'aaa.bbb.ccc'
>>> re.search(r'\..* \.',str)
>>> re.search(r'\..* \.',str,re.X)
<re.Match object; span=(3, 8), match='.bbb.'>
关于正则表达式暂时就讲到这里了,所以NOIP啥时候后能支持一下正则表达式啊(雾
经用户@stdtr1 提醒,c++11好像真的能用正则表达式qwq
给出两篇博客,有兴趣的可以自己研究)
https://www.cnblogs.com/jerrywossion/p/10086051.html
http://cplusplus.com/reference/regex/
以上就是全文了(貌似上万个字了),首先感谢能坚持看到这里qaq
这篇文章稍微简述了一下爬虫的基本方法以及简单应用。其实爬虫还有很多更加高级的操作,同时网站也有很多反爬虫机制。
有些网站会利用浏览器执行js动态生成代码,导致下载的网页与审查元素所看到的代码不一致。有些利用分析用户行为来判断爬虫。当然,爬虫也有对付的办法。
我们可以使用 selenium 配合 webdriver ,使用 python 操控浏览器,模拟浏览器行为;可以配合 Fiddler 抓包来抓取手机APP的数据或者电脑程序的数据;还有爬虫框架 Scrapy 以及其他库函数比如 bs4(beautifulsoup) 解析网页 HTML ,多线程爬虫实现更加强大的功能。由于篇幅限制,这里就不说了)
如果本人没有AFO或者有时间,可能还会再写一篇文章。如果感兴趣可以自行百度搜索,网络上这方面的资料还是很多的,作者本人当时也是自行搜索博客学习的qaq
如果文中有哪里写错了或者有疑问欢迎私信或者评论指出qaq
写文章不易qwq
求评论qwq
求点赞qwq