爬虫项目代理操作和线程池爬取
代理操作
代理操作的目的
一些网站会有相应的反爬虫措施,例如很多网站会检测某一段时间某个IP的访问次数,如果访问频率太快以至于看起来不像正常访客,它可能就会会禁止这个IP的访问。所以我们需要设置一些代理IP,每隔一段时间换一个代理IP,就算IP被禁止,依然可以换个IP继续爬取。
什么是代理
代理服务器:fiddler
提供代理服务器ip的平台:
1.www.goubanjia.com
2.快代理
3.西祠代理
4.代理精灵:http://http.zhiliandaili.cn
为什么使用了代理就可以更改请求对应的ip呢:本机的请求会先发送给代理服务器,代理服务器会接受本机发送过来的请求(当前请求对应的ip就是本机ip),然后代理服务器会将该请求进行转发,转发之后的请求对应的ip就是代理服务器的ip。
代理ip的匿名度
- 透明:使用了透明的代理ip,则对方服务器知道你当前发起的请求使用了代理服务器并且可以监测到你真实的ip
- 匿名:知道你使用了代理服务器不知道你的真实ip
- 高匿:不知道你使用了代理服务器也不知道你的真实ip
代理ip的类型
- http:该类型的代理IP只可以转发http协议的请求
- https:只可以转发https协议的请求
如何构建一个标准的代理IP池
- 1.取各大平台中爬取大量的免费代理ip
- 2.校验出可用的代理ip
-
- 使用每一个代理ip进行请求发送,监测响应状态码是否为200
-
- 3.将可用的代理ip进行存储(redis)
#生成多个代理ip并爬取 all_ips = [] ip_url = 'http://ip.11jsq.com/index.php/api/entry?method=proxyServer.generate_api_url&packid=1&fa=0&fetch_key=&groupid=0&qty=53&time=1&pro=&city=&port=1&format=html&ss=5&css=&dt=1&specialTxt=3&specialJson=' page_text = requests.get(ip_url,headers=headers).text tree = etree.HTML(page_text) ip_list = tree.xpath('//body//text()') for ip in ip_list: ip = {'https':ip} all_ips.append(ip) url = 'https://www.xicidaili.com/nn/%d' for page in range(1,100): print('正在爬取第{}页的数据!'.format(page)) new_url = format(url%page) page_text = requests.get(url=new_url,headers=headers,proxies=random.choice(all_ips)).text tree = etree.HTML(page_text) tr_list = tree.xpath('//*[@id="ip_list"]//tr')[1:] for tr in tr_list: ip = tr.xpath('./td[2]/text()')[0] port = tr.xpath('./td[3]/text()')[0] ip_type = tr.xpath('./td[6]/text()')[0] dic = { 'ip':ip, 'port':port, 'type':ip_type } all_ips.append(dic) print(len(all_ips))
了解cookie和session
无状态的http协议
如上图所示,HTTP协议 是无状态的协议,用户浏览服务器上的内容,只需要发送页面请求,服务器返回内容。对于服务器来说,并不关心,也并不知道是哪个用户的请求。对于一般浏览性的网页来说,没有任何问题。
- 但是,现在很多的网站,是需要用户登录的。以淘宝为例:比如说某个用户想购买一个产品,当点击 “ 购买按钮 ” 时,由于HTTP协议 是无状态的,那对于淘宝来说,就不知道是哪个用户操作的。
- 为了实现这种用户标记,服务器就采用了cookie这种机制来识别具体是哪一个用户的访问。
了解cookie
在上图,为了实现用户标记,在Http无状态请求的基础之上,我们需要在请求中携带一些用户信息(比如用户名之类,这些信息是服务器发送到本地浏览器的,但是服务器并不存储这些信息),这就是cookie机制。
需要注意的是:cookie信息是保存在本地浏览器里面的,服务器上并不存储相关的信息。 在发送请求时,cookie的这些内容是放在 Http协议中的header 字段中进行传输的。
几乎现在所有的网站都会发送一些 cookie信息过来,当用户请求中携带了cookie信息,服务器就可以知道是哪个用户的访问了,从而不需要再使用账户和密码登录。
但是,cookie信息是直接放在Http协议的header中进行传输的,一旦别人获取到你的cookie信息(截获请求,或者使用你的电脑),那么他很容易从cookie中分析出你的用户名和密码。为了解决这个隐患,所以有了session机制。
了解session
因为cookie不安全,所以有了session机制,整过过程是这样:
- 服务器根据用户名和密码,生成一个session ID,存储到服务器的数据库中。
- 用户登录访问时,服务器会将对应的session ID发送给用户(本地浏览器)。
- 浏览器会将这个session ID存储到cookie中,作为一个键值项。
- 以后,浏览器每次请求,就会将含有session ID的cookie信息,一起发送给服务器。
- 服务器收到请求之后,通过cookie中的session ID,到数据库中去查询,解析出对应的用户名,就知道是哪个用户的请求了。
总结
- cookie 在客户端(本地浏览器),session 在服务器端。cookie是一种浏览器本地存储机制。存储在本地浏览器中,和服务器没有关系。每次请求,用户会带上本地cookie的信息。这些cookie信息也是服务器之前发送给浏览器的,或者是用户之前填写的一些信息。
- Cookie有不安全机制。 你不能把所有的用户信息都存在本地,一旦被别人窃取,就知道你的用户名和密码,就会很危险。所以引入了session机制。
- 服务器在发送id时引入了一种session的机制,很简单,就是根据用户名和密码,生成了一段随机的字符串,这段字符串是有过期时间的。
- 一定要注意:session是服务器生成的,存储在服务器的数据库或者文件中,然后把sessionID发送给用户,用户存储在本地cookie中。每次请求时,把这个session ID带给服务器,服务器根据session ID到数据库中去查询,找到是哪个用户,就可以对用户进行标记了。
- session 的运行依赖 session ID,而 session ID 是存在 cookie 中的,也就是说,如果浏览器禁用了 cookie ,那么同时 session 也会失效(但是可以通过其它方式实现,比如在url中传递 session ID)
- 用户验证这种场合一般会用 session。 因此,维持一个会话的核心就是客户端的唯一标识,即session ID
当我们通过抓包工具捕获的基于ajax请求的数据包中提取的url
url = 'https://xueqiu.com/v4/statuses/public_timeline_by_category.json?since_id=-1&max_id=20343389&count=15&category=-1' json_data = requests.get(url=url,headers=headers).json() print(json_data) #打印结果:{'error_description': '遇到错误,请刷新页面或者重新登录帐号后再试', 'error_uri': '/v4/statuses/public_timeline_by_category.json', 'error_data': None, 'error_code': '400016'}
cookie的破解方式
cookie的破解方式 手动处理: 通过抓包工具将请求携带的cookie添加到headers中 弊端:cookie会有有效时长,cookie还是动态变化 自动处理: 使用session进行cookie的自动保存和携带 session是可以进行请求发送的,发送请求的方式和requests一样 如果使用session进行请求发送,在请求的过程中产生了cookie,则该cookie会被自动存储 到session对象中 如果使用了携带cookie的session再次进行请求发送,则该次请求就时携带cookie进行的请求发送 #创建一个session对象 session = requests.Session() #将cookie保存到session对象中 first_url = 'https://xueqiu.com/' session.get(url=first_url,headers=headers)#为了获取cookie且将cookie存储到session中 url = 'https://xueqiu.com/v4/statuses/public_timeline_by_category.json?since_id=-1&max_id=20343389&count=15&category=-1' json_data = session.get(url=url,headers=headers).json()#携带cookie发起的请求 json_data
验证码的处理
处理验证码的实现流程
1.对携带验证码的页面数据进行抓取 2.可以将页面数据中验证码进行解析,验证码图片下载到本地 3.可以将验证码图片提交给三方平台进行识别,返回验证码图片上的数据值 验证码的识别的网站: 超级鹰:http://www.chaojiying.com/about.html 使用流程: 注册:用户中心身份的账号 登陆: 充值一块 创建一个软件:软件ID-》生成一个软件ID 下载示例代码:下载基于python的示例代码 云打码:http://www.yundama.com/demo.html
编辑验证码的类
import requests from hashlib import md5 class Chaojiying_Client(object): def __init__(self, username, password, soft_id): self.username = username password = password.encode('utf8') self.password = md5(password).hexdigest() self.soft_id = soft_id self.base_params = { 'user': self.username, 'pass2': self.password, 'softid': self.soft_id, } self.headers = { 'Connection': 'Keep-Alive', 'User-Agent': 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0)', } def PostPic(self, im, codetype): """ im: 图片字节 codetype: 题目类型 参考 http://www.chaojiying.com/price.html """ params = { 'codetype': codetype, } params.update(self.base_params) files = {'userfile': ('ccc.jpg', im)} r = requests.post('http://upload.chaojiying.net/Upload/Processing.php', data=params, files=files, headers=self.headers) return r.json() def ReportError(self, im_id): """ im_id:报错题目的图片ID """ params = { 'id': im_id, } params.update(self.base_params) r = requests.post('http://upload.chaojiying.net/Upload/ReportError.php', data=params, headers=self.headers) return r.json()
正常网站验证码识别操作
def getCodeImgText(imgPath,imgType): chaojiying = Chaojiying_Client('bobo328410948', 'bobo328410948', '899370')#用户中心>>软件ID 生成一个替换 96001 #第一个是用户名,第二个是密码 im = open(imgPath, 'rb').read()#本地图片文件路径 来替换 a.jpg 有时WIN系统须要// return chaojiying.PostPic(im,imgType)['pic_str'] #古诗文网的验证码识别操作 url = 'https://so.gushiwen.org/user/login.aspx?from=http://so.gushiwen.org/user/collect.aspx' page_text = requests.get(url,headers=headers).text tree = etree.HTML(page_text) img_src = 'https://so.gushiwen.org'+tree.xpath('//*[@id="imgCode"]/@src')[0] print(img_src) img_data = requests.get(url=img_src,headers=headers).content with open('codeImg.jpg','wb') as fp: fp.write(img_data) #进行验证码的识别 getCodeImgText('codeImg.jpg',1004)
使用session机制全程代码
s = requests.Session() #模拟登陆 #古诗文网的验证码识别操作 url = 'https://so.gushiwen.org/user/login.aspx?from=http://so.gushiwen.org/user/collect.aspx' page_text = s.get(url,headers=headers).text tree = etree.HTML(page_text) img_src = 'https://so.gushiwen.org'+tree.xpath('//*[@id="imgCode"]/@src')[0] img_data = s.get(url=img_src,headers=headers).content with open('codeImg.jpg','wb') as fp: fp.write(img_data) #解析动态变化的请求参数 __VIEWSTATE = tree.xpath('//input[@id="__VIEWSTATE"]/@value')[0] __VIEWSTATEGENERATOR = tree.xpath('//input[@id="__VIEWSTATEGENERATOR"]/@value')[0] print(__VIEWSTATE,__VIEWSTATEGENERATOR) #进行验证码的识别 code_text = getCodeImgText('codeImg.jpg',1004) print(code_text) login_url = 'https://so.gushiwen.org/user/login.aspx?from=http%3a%2f%2fso.gushiwen.org%2fuser%2fcollect.aspx' data = { #下面两个请求参数是动态变化 #通长情况下动态变化的请求参数会被隐藏在前台页面中 '__VIEWSTATE': __VIEWSTATE, '__VIEWSTATEGENERATOR': __VIEWSTATEGENERATOR, 'from': 'http://so.gushiwen.org/user/collect.aspx', 'email': 'www.zhangbowudi@qq.com', 'pwd': 'bobo328410948', 'code': code_text, 'denglu': '登录', } #登陆成功之后对应的首页页面源码 main_page_text = s.post(url=login_url,headers=headers,data=data).text with open('./main.html','w',encoding='utf-8') as fp: fp.write(main_page_text)
线程池实现异步爬取
背景
其实爬虫的本质就是client发请求批量获取server的响应数据,如果我们有多个url待爬取,只用一个线程且采用串行的方式执行,那只能等待爬取一个结束后才能继续下一个,效率会非常低。需要强调的是:对于单线程下串行N个任务,并不完全等同于低效,如果这N个任务都是纯计算的任务,那么该线程对cpu的利用率仍然会很高,之所以单线程下串行多个爬虫任务低效,是因为爬虫任务是明显的IO密集型(阻塞)程序。
线程池的实现
from multiprocessing.dummy import Pool #线程池模块 #必须只可以有一个参数 def my_requests(url): return requests.get(url=url,headers=headers).text start = time.time() urls = [ 'http://127.0.0.1:5000/bobo', 'http://127.0.0.1:5000/jay', 'http://127.0.0.1:5000/tom', ] pool = Pool(3) #map:两个参数 #参数1:自定义的函数,必须只可以有一个参数 #参数2:列表or字典 #map的作用就是让参数1表示的自定义的函数异步处理参数2对应的列表或者字典中的元素 page_texes = pool.map(my_requests,urls) print(page_texes) print(time.time()-start)