爬虫学习(二)
bug
环境:Windows10企业版,版本号1809;pycharm2017.3.3。
背景:在将爬取的数据执行写入文件操作时报错。
代码:
with open('baidu2.html','w') as f: f.write(resp2.content.decode())
错误:UnicodeEncodeError: 'gbk' codec can't encode character '\xbb' in position 30633: illegal multibyte sequence。
解决办法:
with open('baidu2.html','w',encoding='utf-8') as f: f.write(resp2.content.decode())
原因分析:在Windows中新建的文本文件默认编码是 gbk
,对于其他格式的数据无法编码,所以报错,我们可以指定编码格式为 utf-8
。
1.使用IP代理
1.什么是Ip代理?
答:IP代理即代理服务器,其功能主要就是代理网络用户去获取网络信息,形象的说就是网络信息的中转站。
2.为什么爬虫需要使用代理?
答:让目标服务器以为不是同一个客户端在请求,防止因为ip发送请求过多而被反爬;防止我们的真实地址被泄漏;防止被追究责任。
3.怎么理解使用代理的过程?
答:代理ip是一个ip,指向的是一个代理服务器;代理服务器能够帮我们向目标服务器转发请求。
正向代理是保护客户端,反向代理是保护服务器。
1.1使用代理
免费IP代理的使用:生产环境下不能使用免费代理IP。
proxy = {
'http':'http://103.230.35.222:3128', 'https':'https://103.230.35.222:3128', } resp = requests.get(url,proxies=proxy)
生产环境下:ip池一般存入数据库,查询数据,随机选择ip来使用。而且需要维护一个ip池,ip池是付费和免费的IP混合使用。
付费IP的使用:user表示使用代理网站的账号,password表示账号密码。
proxy2 = {
'http':'http://user:[email protected]:3128', 'https':'https://user:[email protected]:3128', }
注意:代理IP的协议,如果是HTTP,不能发送HTTPS的请求!!!
2.cookie和session
2.1二者区别
1.cookie数据存放在客户的浏览器上,session数据放在服务器上。
2.cookie不是很安全,别人可以分析存放在本地的cookie并进行cookie欺骗(使用用户的cookies获取相关信息)。
3.session会在一定时间内保存在服务器上。当访问增多,会比较占用服务器资源,降低性能。
4.单个cookie保存的数据不能超过4K,很多浏览器都限制一个站点最多保存20个cookie。
2.2利弊与抉择
带上cookie和session的好处:很多网站必须登录之后(或者获取某种权限之后)才能够请求到相关数据。
带上cookie和session的弊端:一套cookie和session往往和一个用户对应,请求太快,请求次数太多,容易被服务器识别为爬虫。然后被封号,造成损失。
有些账号是很珍贵的,权限等级之类的,被封后损失很严重。
使用建议:
1.不需要cookie的时候尽量不去使用cookie,如必须要用,不要用自己的账号。
2.为了获取登录之后的页面,我们必须发送带有cookies的请求,此时为了确保账号安全应该尽量降低数据采集速度。
2.3案例-使用cookie来获取登录之后人人网的响应
需求:获取人人网需要登录后,才能看到的页面。
cookie的使用第一种:在headers中传入cookie。
url = 'http://www.renren.com/438718956'
headers = {
# 从浏览器中复制过来的User-Agent
'User-Agent': '浏览器的用户代理', # 从浏览器中复制过来的Cookie 'Cookie': 'xxx这里是复制过来的cookie字符串' } # 发送请求 resp = requests.get(url,headers=headers) # 判断是否登录成功,可以判断响应的页面中是否有具有标识的特殊字段,此处账号名是`风雨`,我们可以看是否有风雨两字。 # 使用正则,从结果中匹配`风雨` import re print(re.findall('风雨',resp.content.decode()))
cookie的使用第二种:以字典的形式传入cookie信息。
cookie的本质是键值对形式的字符串。
url = 'http://www.renren.com/438718956'
headers = {
# 从浏览器中复制过来的User-Agent
'User-Agent': '浏览器的用户代理' } temp_str = 'xxx这里是复制过来的cookie字符串' # 我们通过分析cookie字符串,发现里面的数据有规律,都是以等号连接的键值对,然后键值对用封号和空格隔开。因此我们用下列的操作进行提取cookie。 cookie = {} for ck in temp_str.split('; '): key = ck.split('=')[0] value = ck.split('=')[-1] cookie[key] = value resp = requests.get(url,headers=headers,cookies=cookie)
2.4案例-使用session来登录人人网
使用cookie的弊端就是我们需要现在浏览器中登录,然后粘贴cookie信息,比较繁琐低效。
网址:http://www.renren.com/PLogin.do
session的使用:
1.实例化session对象。
2.使用session对象发送请求。
3.获取响应,解析响应数据。
import requests
url = 'http://www.renren.com/PLogin.do'
# 构造post请求的data数据
post_data = {
'email':'131****8225', 'password':'welcome to 小闫笔记' } s = requests.session() resp = s.post(url,data=post_data) import re print(re.findall('风雨',resp.content.decode()))
session会帮我们自动实现状态保持。
2.5requests小技巧
1.cookiesjar与字典之间的相互转换
应用场景:在爬取某些网站的数据,cookie信息动态变化,如果cookie拿不到数据,可以使用 cookiejar
动态获取cookie信息再次发送网络请求。
把 cookiejar
对象转化为字典格式的cookies:
reqeusts.utils.dict_from_cookiejar
把字典格式的cookies转换成 cookiejar
对象:
requests.utils.cookiejar_from_dict
2.请求SSL证书验证
如果访问一个网站,遇到SSL错误信息,原因是该网站的CA认证证书不是标准的。
使用场景:Requests 可以为 HTTPS 请求验证 SSL 证书,就像 web 浏览器一样。SSL 验证默认是开启的,如果证书验证失败,Requests 会抛出 SSLError
使用方式:
response = requests.get("https://www.12306.cn/mormhweb/ ", verify=False)
3.设置超时
使用场景:有些站点或者代理反应慢,严重降低效率,这个时候可以设置超时。
使用方式:
response = requests.get(url,timeout=10)
timeout就是在这个时间过了之后,还是拿不到响应,那么就报错。 timeout:整型值,单位为s。
3.数据提取
什么是数据提取?
答:简单的来说,数据提取就是从响应中获取我们目标数据的过程。
数据分类:
1.非结构化的数据:html,文本等。没有规律的。(此处的没有规律,举个例子来说,就是标签中有单个标签形式也会有成对标签的形式)
处理方法:正则表达式,xpath。
2.结构化数据:json,xml等。符合一定规律的。
处理方法:使用json模块,转化为python数据类型。
3.1数据提取之JSON
1.什么是json?
答:json是一种轻量级的数据交换格式,它使得人们很容易进行阅读和编写。同时也方便了机器进行解析和生产。适用于进行数据交互的场景,比如网站前台和后台之间的数据交互。
2.为什么要使用json?
答:把json格式字符串转换为python字典类型很简单,所以爬虫中,如果我们能够找到返回json数据格式字符串的url,就会尽量使用这种url。
3.如何找到返回json的url?
答:使用浏览器、抓包工具进行分析。
json在数据交换中起到了一个载体的作用,承载着相互传递的数据。json并不是一种数据类型。
json.dumps # 把字典转为json---操作的是变量
json.loads # 把json转成字典
json.dump # 把字典转为json---操作的是文件对象(具有read和write方法的对象) json.load # 把json转成字典
3.2案例-实现豆瓣电视剧爬虫
需求:爬取豆瓣电视的电视名、基本信息、封面。
实现步骤:
1.构建请求信息。
2.发送请求,获取响应。
3.解析响应数据
4.保存数据。
技术点:使用json模块,结构化数据。
import json
import requests
class Douban:
def __init__(self): self.url = 'https://m.douban.com/rexxar/api/v2/subject_collection/tv_korean/items?os=android&for_mobile=1&start={}&count=18' self.start = 0 # 豆瓣反爬的手段: self.headers = { 'User-Agent': 'Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.119 Mobile Safari/537.36', 'Referer': 'https://m.douban.com/tv/korean' } self.file = open('douban.json','w') def get_data(self,url): resp = requests.get(url,headers=self.headers) # print(resp.content.decode()) return resp.content.decode() def parse_data(self,data): # 解析json数据,转成字典 dict_data = json.loads(data) # 提取字典中的电视数据列表 data_list = dict_data['subject_collection_items'] # 遍历数据列表,提取每个电视的数据 tv_list = [] for tv in data_list: temp = {} temp['title'] = tv['title'] temp['info'] = tv['info'] temp['url'] = tv['url'] tv_list.append(temp) # print(tv_list) return tv_list def save_data(self,tv_list): # 保存电视列表数据,遍历电视列表数据,把每条数据,转成json字符串,统一写入文件 for tv in tv_list: json_str = json.dumps(tv,ensure_ascii=False) + ',\n' self.file.write(json_str) # 关闭文件 def __del__(self): self.file.close() def run(self): while True: # 1、构建请求信息 # 2、发送请求,获取响应 url = self.url.format(self.start) data = self.get_data(url) # 3、解析响应数据 tv_list = self.parse_data(data) # 4、保存数据 self.save_data(tv_list) # 5、运行 self.start += 18 # 定义循环的终止条件 if tv_list == []: break if __name__ == '__main__': douban = Douban() douban.run()
在前端中,看到155开头,13位左右的一串数,第一时间就要想到是否为时间戳。
总结:headers中的请求头信息,需要加入referer(从请求中查看的)。json模块的使用(dumps、loads可以用来提取数据,保存文件)。
3.3案例-获取36kr网站首页的新闻
需求:爬取36kr新闻网站的新闻数据,新闻标题、摘要、封面图片
步骤:
1.构建请求信息。
2.发送请求,获取响应。
3.解析响应数据。---正则。
4.保存数据。
5.运行。
案例中的注意点:
1.响应数据放在前端script标签的变量中。
2.使用正则提取后的json数据,有非json字符串。先把数据写文件,在文件中查找错误信息。提取错误信息,将错误过滤掉。
技术点:非结构化数据,页面的html标签中,使用re和json模块。
import json
import re
import requests
class Kr36: def __init__(self): self.url = 'https://36kr.com/' self.headers = { 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.119 Safari/537.36' } self.file = open('36kr.json','w') def get_data(self): resp = requests.get(self.url,headers=self.headers) # print(resp.content.decode()) return resp.content.decode() # 使用正则表达式,从script标签中提取数据 def parse_data(self,data): # ',data)[0] # print(result) json_data = result.split(',locationnal=')[0] # with open('temp.json','w')as f: # f.write(json_data) dict_data = json.loads(json_data) # print(dict_data) data_list = dict_data['feedPostsLatest|post'] # 遍历数据列表,提取新闻数据 news_list = [] for news in data_list: temp = {} temp['title'] = news['title'] temp['summary'] = news['summary'] temp['cover'] = news['cover'] news_list.append(temp) # print(news_list) return news_list def save_data(self,news_list): for news in news_list: json_str = json.dumps(news,ensure_ascii=False) + ',\n' self.file.write(json_str) def __del__(self): self.file.close() def run(self): data = self.get_data() news_list = self.parse_data(data) self.save_data(news_list) if __name__ == '__main__': kr36 = Kr36() kr36.run()
3.4案例-综合练习:有道翻译
需求:爬取有道翻译结果,学习解析js代码。
是结构化数据
在浏览器中找到对应的js文件:
1.ctrl + F,查询关键字(一定是特殊的,不要找普通的,都具有的那种关键字)。
2.network中,找到对应的数据包,在后面找到initiator列中的js文件点进去。
3.根据标签对应的事件监听。
步骤:
1.构建请求信息。
2.发送请求,获取响应。
3.解析响应数据。
4.运行。
重点:
1.js解析的过程,根据需要查找的关键字进行js的解析。
2.有道翻译的反爬措施:请求头重必须要有referer和cookie,缺一不可。
import json
import random
import requests
# python中的哈希加密的模块
import hashlib
import time
""" 生成post请求的data数据: r = "" + (new Date).getTime() i = r + parseInt(10 * Math.random(), 10); ts: r, bv: t, salt: i, sign: n.md5("fanyideskweb" + e + i + "p09@Bn{h02_BIEe]$P^nG") """ class Youdao: def __init__(self,kw): self.url = 'http://fanyi.youdao.com/translate_o?smartresult=dict&smartresult=rule' self.headers = { 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.119 Safari/537.36', 'Referer': 'http://fanyi.youdao.com/', 'Cookie': '[email protected]; JSESSIONID=aaalA_qEvNE-Tacc7zdLw; OUTFOX_SEARCH_USER_ID_NCOO=1283878255.2660573; ___rl__test__cookies=1551692784632' } self.kw = kw self.post_data = None def generate_post_data(self): # r = "" + (newDate).getTime() r = str(int(time.time() * 1000)) # i = r + parseInt(10 * Math.random(), 10); i = r + str(random.randint(0,9)) ts = r salt = i # sign: n.md5("fanyideskweb" + e + i + "p09@Bn{h02_BIEe]$P^nG") temp_str = "fanyideskweb" + self.kw + salt + "p09@Bn{h02_BIEe]$P^nG" # 构造md5对象 md5 = hashlib.md5() # 对要加密的字符串进行编码 md5.update(temp_str.encode()) # 转成16进制 sign = md5.hexdigest() # 构造post请求的参数信息 self.post_data = { 'i': self.kw, 'from': 'AUTO', 'to': 'AUTO', 'smartresult': 'dict', 'client': 'fanyideskweb', 'salt': salt, 'sign': sign, 'ts': ts, 'bv': 'e5845ac95fe54fe7e094f141d5d0cc0d', 'doctype': 'json', 'version': '2.1', 'keyfrom': 'fanyi.web', 'action': 'FY_BY_REALTIME', 'typoResult': False } def get_data(self): resp = requests.post(self.url,headers=self.headers,data=self.post_data) # print(resp.content.decode(