前言
有爬虫就会有反爬,就好像有正版就肯定会有做盗版的一样。
某小博物馆免票展览一批最新出土的文物,张三也想进去看看(,顺便实施盗窃)。
13:00 pm,进去一次,……,出来;
13:01 pm,又进去一次,……,出来;
13:02 pm,又进去一次,引起门口保安注意,……,出来;
13:03 pm,又进去一次,保安认为他进出次数过于频繁,将其认定为贼并乱棒打出
13:04 pm,张三又想进去。。。不好意思,张三再也进不了这个博物馆了
爬虫和反爬就像上面这则故事的张三和保安。正常来说,当你短期内非常暴力地大量访问了某一个或者某一类网页后,你大概率会被人家拉黑。 反爬最基本的策略就是一刀切——把你的IP给封杀了,这是最简单粗暴的方法,但也是对付像我一样的初级爬虫小白比较有效的一招。
张三不甘心,于是第二天叫来了他异父异母的兄弟们【张四、张五、张六……张一百】
13:00 pm,张四进去,……,出来;
13:01 pm,张五进去,……,出来;
13:02 pm,张六进去,……,出来;
……
xx:xx pm,张一百进去,……,出来;
xx:xx+1 pm,张四进去,……,出来;
……
yy:yy pm,博物馆 空
事实上,真正的反爬远比这个复杂得多,张三遇到的只是最最low的保安。但是这个故事也可以说明,对于比较简单的网站的反爬,只需要不停地更换自己的IP地址就能对付。 其原因也很简单,不停地换生面孔,“保安”没有足够的证据证明想进来的是“贼”而拦住他,即使拦住,也找不到幕后主使“张三”。
IP是上网需要唯一的身份地址,身份凭证,而代理IP就是我们上网过程中的一个中间平台,是由你的电脑先访问代理IP,之后再由代理IP访问你点开的页面。
一、怎么找IP代理?
这玩意百度一搜一大把。根据是否要¥,分为免费代理和付费代理。
当然,付费代理的稳定性和可用性不知道比免费的高出多少倍。
但是话又说回来了,人家钱都不收你的就免费给你用,还要啥自行车
搜索关键字“代理IP”,如图
我瞄中了“快代理”,它打出的招牌是“国内高匿免费HTTP代理IP”,那相比应该有许多可以用的吧(虽然最后事实告诉我可以用的仅凤毛麟角)
二、直接上手
先把可用的搞下来
# -*- coding: utf-8 -*-
# GETIP.py
# @Time: 2021/3/29 14:42
# @Software: PyCharm
import time
from requests_html import HTMLSession
import random
def GetID():
session = HTMLSession()
fo = open('代理IP.txt', "w")
# 快代理找代理池,写进文件
useful_IP = []
for page in range(1, 11):
res = session.get("https://www.kuaidaili.com/free/intr/{}".format(page))
IPs = res.html.xpath("//tbody/tr/td[position()<5]")
useful_IP += ["{},{},{}".format(ip[0].text, ip[1].text, ip[2].text) for ip in
zip(IPs[::4], IPs[1::4], IPs[3::4])]
time.sleep(random.randint(3, 5))
fo.write("\n".join(useful_IP))
fo.close()
session.close()
if __name__ == '__main__':
GetID()
再对它进行筛选和分析
# -*- coding: utf-8 -*-
# CHECKIP.py
# @Time: 2021/3/29 17:34
# @Software: PyCharm
import time
import GETIP
import requests
# GETIP.GetID() # 获取IP列表
fp = open('代理IP.txt', mode='r', encoding='utf-8') # 总的ip列表
fp2 = open('可用代理IP.txt', mode='a', encoding='utf-8') # 经过验证的ip
num = 0
useful_IP = fp.read().splitlines()
def verify_proxy():
global num
for line in useful_IP:
ip, host, protocol = line.split(',')
url1 = 'http://ip.tool.chinaz.com/'
url2 = 'https://ip.cn/'
try:
if protocol == 'HTTPS':
requests.get(url2, proxies={'https': '{}:{}'.format(ip, host)}, timeout=5)
print('该 {} ip-> {}:{} 验证通过'.format(protocol, ip, host))
num += 1
fp2.write('{},{},{}\n'.format(ip, host, protocol))
else:
requests.get(url1, proxies={'http': '{}:{}'.format(ip, host)}, timeout=5)
print('该 {} ip-> {}:{} 验证通过'.format(protocol, ip, host))
num += 1
fp2.write('{},{},{}\n'.format(ip, host, protocol))
except Exception as e:
print('该 {} ip-> {}:{} 验证失败'.format(protocol, ip, host))
with open('错误日志.txt', mode='a', encoding='utf-8') as fe:
fe.write('{}\n{}\n{}\n\n'.format('验证代理', str(e), time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())))
return num
verify_proxy()
fp.close()
fp2.close()
print('可用ip的数量是{}'.format(num))
三、代码的合并、重构和优化
有用到多线程,头一次启用需要用到文件里写死的proxy_list,这几个也是我爬下来的,之后估计会失效,第一次用的时候需要自己先找几个可靠的代理来跑。从第二次开始,便会读取在本地创建的存有可用代理的文件,可以在后台写个脚本,每天定时自动跑一次,基本上能够保证《可用代理IP.txt》中每天都是自己的最新的可用代理池了。
# -*- coding: utf-8 -*-
# @Time: 2021/3/30 11:49
# @Author: 胡志远
# @Software: PyCharm
import time
from requests_html import HTMLSession
import random
import threading
from queue import Queue
from pathlib import Path
def get_proxy_list(is_UsefulIPs):
if is_UsefulIPs:
fp = open('可用代理IP.txt', mode='r') # 可用ip列表
else:
fp = open('代理IP.txt', mode='r') # 总的ip列表
IPs = set(fp.read().splitlines()) # 去重
fp.close()
format_IP = []
for line in IPs:
ip, host, protocol = line.split(',')
if protocol == 'HTTPS':
format_IP.append({'https': '{}:{}'.format(ip, host)})
elif protocol == 'HTTP':
format_IP.append({'http': '{}:{}'.format(ip, host)})
return format_IP
global proxy_list
# 不同的代理IP,代理ip的类型必须和请求url的协议头保持一致
if Path("可用代理IP.txt").is_file():
proxy_list = get_proxy_list(is_UsefulIPs=True)
else:
# 这个列表要用的话最好更新
proxy_list = [
{"http": "59.124.224.180:3128"}
, {'http': '45.169.16.22:8080'}
, {'http': '86.62.120.68:3128'}
, {'http': '45.188.156.129:8080'}
, {'http': '186.10.118.189:8081'}
]
class Thread_crawl(threading.Thread):
def __init__(self, name, page_queue):
threading.Thread.__init__(self)
self.page_queue = page_queue
self.name = name
def run(self) -> None:
while True:
if self.page_queue.empty():
break
else:
print(self.name, '将要从队列中取任务')
page = self.page_queue.get()
print(self.name, '取出的任务是:', page)
url = 'https://www.kuaidaili.com/free/intr/{}'.format(page)
self.get_content(url=url)
print(self.name, '完成任务的页码是:', page)
def get_content(self, url):
time.sleep(random.randint(3, 8))
proxy = random.choice(proxy_list)
self.get_data(session.get(url, proxies=proxy).html)
def get_data(self, response):
IPs = response.xpath("//tbody/tr/td[position()<5]")
useful_IP = ["{},{},{}".format(ip[0].text, ip[1].text, ip[2].text) for ip in
zip(IPs[::4], IPs[1::4], IPs[3::4])]
if useful_IP:
fo.write("\n".join(useful_IP) + "\n")
def GetID(pages):
page_queue = Queue()
for page in range(1, pages):
page_queue.put(page)
# 生成线程
crawl_name = ['c1', 'c2', 'c3', 'c4', 'c5']
crawl_tread = []
for name in crawl_name:
crawl = Thread_crawl(name, page_queue)
crawl.start()
crawl_tread.append(crawl) # 存放进程,堵塞主进程
# 堵塞主线程,让子线程都完成任务后,主线程在往下执行
for thread in crawl_tread:
thread.join()
class Thread_analyse(threading.Thread):
url1 = 'http://ip.tool.chinaz.com/'
url2 = 'https://ip.cn/'
def __init__(self, name, IP_queue):
threading.Thread.__init__(self)
self.IP_queue = IP_queue
self.name = name
def run(self) -> None:
while True:
if self.IP_queue.empty():
break
else:
print(self.name, '将要从队列中取任务')
IP = self.IP_queue.get()
print(self.name, '取出的任务是:', IP)
self.tryGet(IP)
def tryGet(self, ip_proxy):
try:
if ip_proxy.get('http'):
session.get(self.url1, proxies=ip_proxy, timeout=5)
fo2.write('{},HTTP\n'.format(ip_proxy.get('http').replace(':', ',')))
else:
session.get(self.url2, proxies=ip_proxy, timeout=5)
fo2.write('{},HTTPS\n'.format(ip_proxy.get('https').replace(':', ',')))
print('{} 验证通过'.format(ip_proxy))
except Exception:
print('{} 验证失败'.format(ip_proxy))
def CheckIP():
IP_queue = Queue()
format_IP = get_proxy_list(is_UsefulIPs=False)
for ip in format_IP:
IP_queue.put(ip)
# 生成线程
analyse_name = ['C1', 'C2', 'C3', 'C4', 'C5']
analyse_tread = []
for name in analyse_name:
analyse = Thread_analyse(name, IP_queue)
analyse.start()
analyse_tread.append(analyse) # 存放进程,堵塞主进程
# 堵塞主线程,让子线程都完成任务后,主线程在往下执行
for thread in analyse_tread:
thread.join()
if __name__ == '__main__':
session = HTMLSession()
## 重新获取最新IP列表
fo = open('代理IP.txt', mode='w') # 总的ip列表
GetID(51) # 爬50页
fo.close()
print("~" * 35) # 分割线
## 筛选可用代理IP
fo2 = open('可用代理IP.txt', mode='w') # 经过验证的ip
CheckIP()
fo2.close()
session.close()
【代码运行截图】
吐槽一句,免费的实不靠谱,可用的真太少了。
总结
请遵守道德和法律的约束,不要违法乱纪