IP访问频率限制是网页中最常用的反爬虫手段,当爬虫的IP被目标服务器ban掉之后,我们的爬虫代码是没办法继续正常执行的。解决这个问题的方法也很简单就是用网页代理,但是现在免费的网页代理不好找,收费的又太贵。可是日常又需要,我就想着通过爬取免费代理网站的免费代理来构建自己的IP代理池。
总共分为三个小的模块:代理的爬取(爬虫模块)、数据的筛选、代理服务的搭建。我想一下用现在我的技术栈是可以实现的:就用requests+BeautifulSoup4、Flask就可以实现我们需要的功能,数据持久话就用MySQL。看起来并不是一个很复杂的项目,我先去Github上看看有没有类似的项目。
Github上果然有类似的项目:ProxyPool爬虫IP代理池。项目实现了部份免费代理的爬取测试和筛选,并且支持扩展自己的爬虫代码。同时我看到获取代理的接口作者是用Flask开发的,这简直和我的想法一模一样哈哈哈。这么想想假如我早几年有这个想法并把这个想法付诸实践我是不是也能有7K stars的开源项目呢?意淫大家都会,我肯定是没有作者这么高的技术了。但是由于出于学习的目的,我还是要写一个拙劣版的ProxyPool。为了学习嘛(●ˇ∀ˇ●)。
首先声明代码拙劣是真的很拙劣,主要是实现功能并不是做框架,所以不考虑拓展的问题。只是一个练手项目而已!!!
前期准备
数据源是来自西拉免费IP代理。为什么?因为数据是真的多啊,免费代理都是几千页起步的,搞得我心痒痒的。但是说实话质量是着实一般啊,就是因为代理质量十分一般导致我几乎中途放弃。但是爬虫部份都写好了,放在那里也是浪费不如发出来让大家喷一喷。
运行环境介绍: Python 3.7.2 PyMySQL(数据库支持) requests(Http支持) BeautifulSoup4(数据提取) Tqdm(可视化进度条)
所有库几乎都是用的最流行的支持库,不是因为他们好用我才学他们,是因为他们流行我才学。不过事实证明流行的原因确实是因为超级好用啊。
网站的反爬机制
大部分数据网站都有反爬虫的机制,更何况是提供免费代理的网站。但是经过我的研究发现西拉代理的反爬只有两个方面:①限制单IP的访问频率,相同IP访问频繁之后会进入隐藏页面,这个页面就是网站让我们购买收费接口的界面。确切的来说是一个优惠券页面,足以可见用心良苦啊。②:如果两次请求间隔过快的话,服务器会忽略分页数据。即使改变了url的页码,可能返回的数据也都是相同的。
因为我们是爬取网页代理,但是爬取网页代理又要用到网页代理,好像陷入了一个死循环啊😨?最初我是想通过从前面爬取的代理中随机选择几条能用的给现在的请求用,无奈代理质量实在一般,代码找了半天都没有找到可用的代理。索性直接用ProxyPool现成的服务http://118.24.52.95/获取一个可用的代理,问题解决了。
数据库准备
数据的持久化是采用MySQL,别问我为什么,问就是因为NoSQL我还不会用。就连关系型数据库我都不是很会,把Navicat生成的建表SQL贴一下。我是用可视化建表一行一行配置的😅
CREATE TABLE `proxy` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键', `ip` varchar(24) NOT NULL COMMENT 'ip地址', `addr` varchar(255) DEFAULT NULL COMMENT '地理位置', `mode` tinyint(2) DEFAULT '0' COMMENT '代理模式:0http,1https,2http(s)', `score` int(11) DEFAULT '0' COMMENT '评分', `test_num` int(11) DEFAULT '0' COMMENT '检测可用次数', `query_num` int(11) DEFAULT '0' COMMENT '调用次数', `query_time` int(11) DEFAULT NULL COMMENT '上次查询的时间', `create_time` int(11) DEFAULT NULL COMMENT '创建时间', PRIMARY KEY (`id`), UNIQUE KEY `ip` (`ip`) ) ENGINE=InnoDB AUTO_INCREMENT=787038 DEFAULT CHARSET=utf8mb4;
具体的业务代码
因为代理网页一共有四种代理类型,每种类型有2000页的代理,每页有50行代理。在理想状态下大概有4ow的数据。但是由于我的代码过于拙劣我还没有完整的将整个网页跑一遍过,到现在数据库也只有7w多条无重复的数据。究其原因大致是因为想解决异步插入数据库而忘记给爬虫添加线程了,such a stupid head!但是我也不想改了。
因为我害怕数据量太大插入数据库会吃不消,就想着通过任务队列来异步插入数据库。但是事实证明由于爬虫的龟速数据库轻松carry,是我大意了。
只需要简单的改一下数据库的配置就可以使用,但是效率实在不敢恭维,拙劣,代码很拙劣!代码我放在最后了,插到中间实在不雅观不是么?后面我试着把代码都放到Github上,写的东西太水了我都没脸放上去实在是丢人。
总结
因为代理的质量不佳我几乎放弃了这个念头,希望后面能好好改进一下代码,爬虫的效率有点低。主要还是发送请求部份,说来搞笑这个代码的目的本来就是想解决这个问题不是么?
最近在用Flask实现微信公众号的自定义开发,主要是想写个机器人啥的自己用着玩。毕竟只有两个人关注我的公众号,一个是我一个是我的小号,嘎嘎嘎。
import queue import time from datetime import datetime from threading import Thread import pymysql import requests from bs4 import BeautifulSoup as bs from tqdm import tqdm sql = ''' insert into weiney.proxy (ip, addr, mode, score, query_time, create_time) value (%(ip)s, %(addr)s, %(mode)s, %(score)s, %(create_time)s, %(create_time)s) ''' class ProxySpider(): headers = { 'User-Agent': "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:67.0) Gecko/20100101 Firefox/67.0" } url_list = [] url_list.append("http://www.xiladaili.com/putong/{}/") url_list.append("http://www.xiladaili.com/gaoni/{}/") url_list.append("http://www.xiladaili.com/http/{}/") url_list.append("http://www.xiladaili.com/https/{}/") def __init__(self, data_queue: queue.Queue): self.queue = data_queue self.proxies = {} self.__get_proxy() def spider_get(self, page): for url in self.url_list: url = url.format(page) while True: try: response = requests.get(url, headers=self.headers, proxies=self.proxies) except: self.__get_proxy() continue if response.status_code == 200: try: self.__parse_data(response.text) break except Exception as e: print(response.status_code, e.args) self.__get_proxy() def __parse_data(self, data): soup = bs(data, "html.parser") table = soup.find("tbody") items = table.find_all("tr") for item in items: detail = item.find_all("td") data = { "ip": detail[0].text, "mode": detail[1].text, "addr": detail[3].text, "score": detail[-1].text } self.queue.put(data) def __get_proxy(self): try: response = requests.get("http://118.24.52.95/get/").json() self.proxies["http"] = "http://{}".format(response.get("proxy")) self.proxies["https"] = "https://{}".format(response.get("proxy")) except: pass time.sleep(5) class DataSave(): def __init__(self, addr, user, password, db_name): self.db = pymysql.connect(addr, user, password, db_name) self.cursor = self.db.cursor() def data_insert(self, sql, data): try: self.cursor.execute(sql, data) self.db.commit() return True except Exception as e: if e.args[0] != 1062: print(e.args) self.db.rollback() return False def __del__(self): self.db.close() def stamp_now(): stamp = datetime.now().timestamp() return int(stamp) def data_process(queue: queue.Queue): global total global single db = DataSave("localhost", "weiney", "123456", "weiney") while not queue.empty() or single: if queue.empty() and single: continue data = queue.get() if data["mode"] == "HTTP代理": data["mode"] = 0 elif data["mode"] == "HTTPS代理": data["mode"] = 1 elif data["mode"] == "HTTP,HTTPS代理": data["mode"] = 2 else: data["mode"] = 0 data["create_time"] = stamp_now() status = db.data_insert(sql, data) queue.task_done() if status: total = total + 1 if __name__ == '__main__': data_queue = queue.Queue() a = ProxySpider(data_queue) single = True total = 0 thread = Thread(name="data_process", target=data_process, args=(data_queue,)) thread.start() pbar = tqdm(total=2000) for x in range(1, 2001): a.spider_get(x) pbar.set_description("当前入库条数:{}条".format(total)) pbar.update(1) single = False
安贞
https://github.com/jhao104/proxy_pool
已经有一个开源项目了,一样的问题质量不高