urllib库简介:
urllib库的response对象是先创建http,request对象,装载到reques.urlopen里完成http请求。
urllib库的作⽤
爬⾍的第⼀个步骤是获取⽹页,urllib库是⽤来实现这个功能:想服务器发送请求,得到服务器响应,获取⽹页的内容。
Python的强⼤在于提供了功能齐全的类库,来帮助我们完成这个请求,通过调⽤urllib库,我们不需要了解请求的数据结构,HTTP,TCP,IP层⽹络传输同学,以及服务器应答原理等。
我们只需要关⼼以下三点,然后通过⼏⾏调⽤urllib库的代码,就能够获得我们想要的⽹页内容。
请求的URL是什么
传递的参数是什么
如何设置可选的请求头
urllib库的构成
在python2中,曾经有urllib和urllib2两个库来实现请求的发送,但⽬前通⽤的python3版本中,两个库的功能已经合并成⼀个库,统⼀为urllib,它是python内置函数,不需要
额外安装即可使⽤。
urllib的四个模块
【1】requset:HTTP请求模块,可以⽤来模拟发送请求,只需要传⼊URL及额外参数,就可以模拟浏览器访问⽹页的过程。
【2】error:异常处理模块,检测请求是否报错,捕捉异常错误,进⾏重试或其他操作,保证程序不会终⽌。
【3】parse:⼯具模块,提供许多URL处理⽅法,如拆分、解析、合并等。
【4】robotparser:识别⽹站的robots.txt⽂件,判断哪些⽹站可以爬,哪些⽹站不可以爬,使⽤频率较少。
发送请求
urlopen是request模块中的⽅法,⽤于抓取⽹络。
我们以代码⽰例,我们抓取百度的⽹页
# 调⽤urllib库中的request模块
import urllib.request
# 发送请求获取百度⽹页的响应
response = urllib.request.urlopen("http://www.baidu.com")
返回的是http,response对象,实际上是html属性。使用.read().decode()解码后转化成了str字符串类型,decode解码后中文字符能够显示出来。
# 打印响应内容
# read()是把响应对象内容全部读取出来,读取出来为bytes码
# decode('utf-8')把bytes码解码
print(response.read().decode('utf-8'))
返回的结果⽐较多,随便截取其中⼀部分,可以看出是百度的⽹页HTML源代码。
我们只⽤⼏⾏代码,就完成了百度的抓取,并打印了⽹页的源代码,接下来,我们看⼀看我们获得的响应内容response到底是什么?利⽤type()⽅法来输出响应的类型。
print(type(response))
注意:
通常爬取网页,在构造http请求的时候,都需要加上一些额外信息,什么Useragent,cookie等之类的信息,或者添加代理服务器。往往这些都是一些必要的反爬机制。
requests库
简介:
requests库调用是requests.get方法传入url和参数,返回的对象是Response对象,打印出来是显示响应状态码。
通过.text 方法可以返回是unicode 型的数据,一般是在网页的header中定义的编码形式,而content返回的是bytes,二级制型的数据,还有 .json方法也可以返回json字符串。
如果想要提取文本就用text,但是如果你想要提取图片、文件等二进制文件,就要用content,当然decode之后,中文字符也会正常显示。
requests的优势:
Python爬虫时,更建议用requests库。因为requests比urllib更为便捷,requests可以直接构造get,post请求并发起,而urllib.request只能先构造get,post请求,再发起。
response.text和response.content的区别:
response.content :这个是直接从网络上抓取的数据,没有经过任何的编码,所以是一个bytes类型,其实在硬盘上和网络上传输的字符串都是bytes类型
response.text:这个是str的数据类型,是requests库将response.content进行解码的字符串,解码需要指定一个编码方式,requests会根据自己的猜测来判断编码的方式,所以有时候可能会猜测错误,就会导致解码产生乱码,这时候就应该进行手动解码,比如使用response.content.decode('utf-8')
response.text和response.content的区别:
response.content :这个是直接从网络上抓取的数据,没有经过任何的编码,所以是一个bytes类型,其实在硬盘上和网络上传输的字符串都是bytes类型
response.text:这个是str的数据类型,是requests库将response.content进行解码的字符串,解码需要指定一个编码方式,requests会根据自己的猜测来判断编码的方式,所以有时候可能会猜测错误,就会导致解码产生乱码,这时候就应该进行手动解码,比如使用response.content.decode('utf-8')
ASCII
ASCII编码的全称是American Standard Code for Information Interchange,美国信息互换标准代码。这是一种最早的、只用来保存英文文字的编码方式。ASCII编码方式只使用了1个字节(8比特位,可以组合出256种不同的状态)中0~127种组合存储了英文的文字。
GBK
当计算机普及到国内时,因为汉字的常用字就有将近6000个,使用ASCII编码已经完全不能满足使用的需求了。
所以在1981年,国家标准总局发布了GB2312(中国国家标准简体中文字符集),使用2个字节的组合,当两个大于127的字符连在一起时,就表示一个汉字,这样就组合出了7000多个简体字。
后来因为汉字的扩展需求,发布了GBK标准,K是扩展一次汉语拼音的声母。即不再要求第二个字节大于127,只要第一个字节大于127,则表示这是一个汉字的开始。这样共收录了将近22000个汉字和符号。且兼容了GB2312标准。
2005年时修订了GB18030标准,支持了国内少数民族的文字,共收录汉字70000余个。兼容了GBK标准。
Unicode
就如国内定义了GB2312标准一样,当时各个国家都规定了适用于自己语言的一套编码方式。但是这就导致各国相互之间谁也不懂谁的编码,装错字符系统就会导致显示全是乱码。
所以这时ISO(International Standards Organization,国际标准化组织)推出了Unicode标准用以解决这个问题。
Unicode标识以2个字节长度的数字来标识所有字符,除了英文以外的字符全部重新进行了统一编码。
注意Unicode只是一种标准,不是编码方式,给予了每个字符一个16比特位的数字标识,至于这个字符在内存中是由几个字节存储,并不是Unicode标准规定的。
UTF-8
Unicode标准制定后,在很长的一段时间内无法推广,直到互联网的普及,强烈要求出现一种统一的编码方式。然后就诞生了UTF-8,这个使用Unicode标准的编码方式。注意:因此,UTF-8是Unicode标准的一种实现方式。
UTF-8的编码规则很简单,只有两条:
对于单字节的符号,字节的第一位设为0,后面7位为这个符号的 Unicode 码。因此对于英语字母,UTF-8 编码和ASCII码是相同的。
对于n字节的符号(n > 1),第一个字节的前n位都设为1,第n + 1位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的 Unicode 码。
下表为编码规则,字母x表示可用编码的位。
Unicode符号范围
UTF-8编码方式(二进制)
0000 0000-0000 007F
0xxxxxxx
0000 0080-0000 07FF
110xxxxx 10xxxxxx
0000 0800-0000 FFFF
1110xxxx 10xxxxxx 10xxxxxx
0001 0000-0010 FFFF
11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
以汉字严为例,演示如何实现UTF-8编码:
严的Unicode码是4E25(100111000100101),根据上表,可以发现4E25处在第三行的范围内(0000 0800 - 0000 FFFF),因此严的 UTF-8 编码需要三个字节,即格式是1110xxxx 10xxxxxx 10xxxxxx。然后,从严的最后一个二进制位开始,依次从后向前填入格式中的x,多出的位补0。这样就得到了,严的 UTF-8 编码是11100100 10111000 10100101,转换成十六进制就是E4B8A5。
由此可见,汉字的Unicode码和UTF-8编码是不同的,它们之间可以通过规则进行转换。
注意 : 汉字的Unicode码是2个字节,而UTF-8码是3个字节
Python 中的编码方式转换
Python3中的字符序列类型有两种:str和bytes。
bytes对象是一串十六进制格式字符序列,如b'\xe6\x88\x91',前方的b标示这串字符是bytes对象。
将bytes对象通过上述的某种编码方式可以解析为字符串,如将b'\xe6\x88\x91'使用UTF-8编码方式解码得到汉字我,而使用GBK编码方式解码无法得到完整的汉字。
Python3中使用encode()和decode()进行字符与二进制序列之间的转换,可以这样理解:encode(编码)就是把人能看懂的汉字,转换为机器能看懂的二进制序列。decode(解码)就是把人看不懂的二进制序列,转换为汉字。
使用encode()和decode()时,有个encoding参数,默认值为UTF-8,指定了对字符串进行编码或解码时,使用的编码方式。
有关文件的编码方式
前面说了那么多,还没有讲到有关文件的编码方式,而且可能平时使用open()打开文件read()的时候,并没有指定编码方式,也能够正常打印出来文件的内容。
文件的编码方式是在open()是指定的,有个encoding参数,作用和字符串解码一样,如果以非二进制模式(b)打开文件,会默认通过UTF-8方式打开。
所以一份GBK编码的文件,如果不以二进制模式打开、且不设置这个encoding参数,是会报解码错误的(UnicodeDecodeError)。
当然,如果以二进制模式打开文件,再读取到的文本就已经是二进制序列了,不涉及encode问题,而是该以什么解码方式将二进制序列转换为人可以读懂的汉字。
所以对于一个未知编码方式的文件,如何通过代码获取其编码方式,然后转换为我们所期望的编码方式呢?
Python提供了一个第三方库chardet,是char detect的缩写,字符监测。
将二进制序列传入chardet.detect()方法,然后会返回一个字典。该字典有3个键值。
{
'encoding': 'GB2312',
'confidence': 0.99,
'language': 'Chinese'
}
encoding是所识别出来该二进制序列的编码方式。confidence是所识别出来的encoding正确概率(1.0表示100%)。language是该编码方式适用的语言。
可以根据confidence概率决定是否使用encoding编码方式。
伪造请求头
import requests
url = 'https://www.zhihu.com/question/315387406/answer/812734512'
headers = {
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36"
}
response = requests.get(url=url, headers=headers)
print(response.status_code) # 200
fake_useragent
from fake_useragent import UserAgent
# 实例化 user-agent 对象
ua = UserAgent()
url = 'https://www.zhihu.com/question/315387406/answer/812734512'
headers = {"user-agent": ua.chrome} # 指定浏览器 user-agent
# 或者可以这样写
# headers = {"user-agent": UserAgent().random} # 一步到位,随机生成一个 user-agent
response = requests.get(url=url, headers=headers)
print(response.status_code) # 200
About
什么是fake_useragent?
简单来说,fake_useragent能灵活的帮助我们生成user-agent。
install
pip install fake_useragent
update
pip install -U fake-useragent
查看版本
import fake_useragent
print(fake_useragent.VERSION) # 0.1.11
Usage
生成指定浏览器的user-agent
import fake_useragent
# 实例化 user-agent 对象
ua = fake_useragent.UserAgent()
# ua.ie
print(ua.ie) # Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0; chromeframe/13.0.782.215)
# ua.msie
print(ua['Internet Explorer']) # Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; InfoPath.2; SLCC1; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; .NET CLR 2.0.50727)
# ua.opera
print(ua.opera) # Opera/9.80 (Windows NT 6.1; U; en-US) Presto/2.7.62 Version/11.01
# ua.chrome
print(ua.chrome) # Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.16 Safari/537.36
# ua.google
print(ua['google chrome']) # Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.0 Safari/537.36
# ua.firefox
print(ua.firefox) # Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:16.0.1) Gecko/20121011 Firefox/21.0.1
# ua.ff
print(ua.ff) # Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:25.0) Gecko/20100101 Firefox/29.0
# ua.safari
print(ua.safari) # Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-TW) AppleWebKit/533.19.4 (KHTML, like Gecko) Version/5.0.2 Safari/533.18.5
随机生成user-agent
import fake_useragent
# 实例化 user-agent 对象
ua = fake_useragent.UserAgent()
print(ua.random) # Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.67 Safari/537.36
print(ua.random) # Mozilla/5.0 (compatible; MSIE 10.0; Macintosh; Intel Mac OS X 10_7_3; Trident/6.0)
print(ua.random) # Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36
每次都能随机生成一个UA表示,大大增强了爬虫的真实性。
爬虫程序运行速度是很快,在网站爬取数据时,一个固定 IP 的访问频率就会很高,这不
符合人为操作的标准。所以一些网站会设置一个 IP 访问频率的阈值,如果一个 IP 访问频率
超过这个阈值,说明这个不是人在访问,而是一个爬虫程序。
方法一:设置延时
import time, random
from urllib import request
import chardet
# 获取新浪国内新闻的前十页的新闻数据
base_url = "http://api.roll.news.sina.com.cn/zt_list?
channel=news&cat_1=gnxw&cat_2==gdxw1||=gatxw||=zs�pl||=mtjj&level==1||=2&show_ext=1&show_all=1&show_num=22&tag=1
&format=json&page={}&callback=newsloadercallback&_=1534912318040"
# 构建前十页的 url
target_urls = [ base_url.format(x) for x in range(1, 11)]
# enumerate 方法类似于 jQuery 中的 each 方法,返回迭代对象的索引和迭代对象的迭代数据
for index, url in enumerate(target_urls):
# 如果我们访问的数据是很多页,这时候如果频繁的访问数据
# 可能会触发反爬机制,所以我们可以设置延时,模拟人类行为,如
time.sleep(random.randint(1,10))
# 伪造 UA
req = request.Request(url)
req.add_header("User-Agent", "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0)")
# 发送请求和获取数据
response = request.urlopen(req)
content = response.read()
# 嗅探编码
charset = chardet.detect(content)['encoding']
html = content.decode(charset)
# 保存数据
with open("html/{}.json".format(index), "w", encoding=charset) as f:
f.write(html)
方法二:代理 IP 地址
爬虫去爬取网站数据的数据的时候,如果单位时间内爬取频次过高,或者其他的原因,被对方识别出来,ip可能会被封禁。这种情况下,通过使用代理ip来解决,作为反爬的策略。
代理ip匿名度:
透明的: 服务器知道了你使用代理ip,也知道你真实的ip
匿名代理: 知道使用了代理ip,不知道真实的ip
高匿代理: 不知道使用了代理ip,也不知道真实的ip(最好的选择)
查看ip 的方法:
在cmd输入命令行:ipconfig(内网的ip——私有的地址)
浏览器访问:ipip.net (外网,上网的ip)
如果设置代理ip,可以通过httpbin.org/ip来查看。不设置代理ip查看则显示我们的真实ip。即上边的外网ip,上网的ip。
代理可以从快代理或豌豆代理处付费获得。(也有可以免费试用的的几个)
查看我们的真实ip的代码:
import requests
url = "http://httpbin.org/ip"
res = requests.get(url)
print(res.text)
复制
输出结果显示真实ip。
设置代理ip:
# 设置一个代理ip,以字典形式呈现,代理ip写在字典值中
proxy = {
'http': 'xxx.xx.xxx.xxx:xxxx'
}
res = requests.get(url, proxies=proxy)
print(res.text)
复制
输出结果显示设置的代理ip。
选出5个ip为例,从中随机选择一个试用,选5次,且出现异常时避免报错。时间间隔设置为1。
'''
36.6.149.154:xxx
114.233.125.55:xxxxx
117.26.229.24:xxx
122.241.27.24:xxxxx
61.132.171.215:xxx
'''
import random
ips = [('36.6.149.154:xxx'),('114.233.125.55:xxxxx'),('117.26.229.24:xxx'),('122.241.27.24:xxxxx'),('61.132.171.215:xxx'),]
url = 'http://httpbin.org/ip'
for i in range(5):
try:
ip = random.choice(ips)
res = requests.get(url, proxies={'http':ip}, timeout=1)
print(res.text)
except Exception as e:
print('出现异常', e)