首页 > 应用开发 > Java > 一波爬虫,直接把年终奖干没了
引言
解决办法
方案1:
方案2:
方案1 实现
方案1伪代码实现 我有提到过对于读多写少的线程安全的容器我们可以选择 CopyOnWrite 容器。
static CopyOnWriteArraySet blackIpCopyOnWriteArraySet = null ;
/**
* 初始化
*/
@PostConstruct
public void init() {
// 调用反爬系统接口 拉取批量黑名单
List<String> blackIpList = getBlackIpList();
// 初始化
blackIpCopyOnWriteArraySet = new CopyOnWriteArraySet(blackIpList);
}
/**
* 判断IP 是否黑名单
* @param ip
* @return
*/
public boolean checkIpIsBlack(String ip) {
boolean checkIpIsBlack = blackIpCopyOnWriteArraySet.contains(ip);
if (!checkIpIsBlack )
return false ;
// 不在redis白名单里面
if (!RedisUtils.exist(String.format( "whiteIp_%" , ip)){
return false ;
}
return true ;
}
上线后经过一段时间让爬虫系统消费我们的请求日志,经过一定模型特征的训练,效果还是很明显的。由于大部分都是爬虫很多请求直接就被拦截了,所以线上的机器可以直接缩容掉一部分了又回到了6台。但是好景不长,突然发现
GC
次数频繁告警不断。为了暂时解决问题,赶紧把生产机器进行重启(生产出问题之后,除了重启和回退
网站交易
还有什么解决办法吗),并且保留了一台机器把它拉出集群,重启之后发现过又是一样的还是没啥效果。通过
dump
线上的一台机器,通过
MemoryAnalyzer
分析发现一个大对象就是我们存放
IP
的大对象,存放了大量的的IP数量。这个IP存放的黑名单是放在一个全局的静态
CopyOnWriteArraySet
,所以每次
gc
它都不会被回收掉。只能临时把线上的机器配置都进行升级,由原来的8核16g直接变为16核32g,新机器上线后效果很显著。
为啥测试环境没有复现?
测试环境本来就没有什么其他请求,都是内网
IP
,几个黑名单
IP
还是开发手动构造的。
解决方案
业务系统不再维护 IP 黑名单池子了,由于黑名单来自反爬系统,爬虫黑名单的数量不确定。所以最后决定采取方案2和方案1结合优化。
什么是布隆过滤器
布隆过滤器(英语:Bloom Filter)是1970年由布隆提出的。它实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。
上述出自百度百科。
说白了布隆过滤器主要用来判断一个元素是否在一个集合中,它可以使用一个位数组简洁的表示一个数组。它的空间效率和查询时间远远超过一般的算法,不过它存在一定的误判的概率,适用于容忍误判的场景。如果布隆过滤器判断元素存在于一个集合中,那么大概率是存在在集合中,如果它判断元素不存在一个集合中,那么一定不存在于集合中。
实现原理
布隆过滤器的原理是,当一个元素被加入集合时,通过 K 个散列函数将这个元素映射成一个位数组(Bit array)中的 K 个点,把它们置为 1 。检索时,只要看看这些点是不是都是1就知道元素是否在集合中;如果这些点有任何一个 0,则被检元素一定不在;如果都是1,则被检元素很可能在(之所以说“可能”是误差的存在)。底层是采用一个bit数组和几个哈希函数来实现。
下面我们以一个
bloom filter
插入"
java
"
和"
PHP
"
为例,每次插入一个元素都进行了三次hash函数
java第一次hash函数得到下标是2,所以把数组下标是2给置为1
java第二次Hash函数得到下标是3,所以把数组下标是3给置为1
java第三次Hash函数得到下标是5,所以把数组下标是5给置为1
PHP 第一次Hash函数得到下标是5,所以把数组下标是5给置为1
...
查找的时候,当我们去查找
C++
的时候发现第三次
hash
位置为0,所以
C++
一定是不在不隆过滤器里面。但是我们去查找“
java
”
这个元素三次
hash
出来对应的点都是1。只能说这个元素是可能存在集合里面。
使用BloomFilter
引入pom
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version> 23.0 </version>
</dependency>
public static int count = 1000000 ;
private static BloomFilter<String> bf = BloomFilter.create(Funnels.stringFunnel(Charset.forName( "utf-8" )), count, 0.009 );
public static void main(String[] args) {
int missCount = 0 ;
for ( int i = 0 ; i < count; i++) {
bf.put(i+ "" );
}
for ( int i = count; i < count+ 1000000 ; i++) {
boolean b = bf.mightContain(i + "" );
if (b) {
missCount++;
}
}
System.out.println( new BigDecimal(missCount).divide( new BigDecimal(count)));
}
解决问题
布隆过滤器介绍完了,我们再回到上述的问题,我们把上述问题通过伪代码来实现下;
/**
* 初始化
*/
@PostConstruct
public void init() {
// 这个可以通过配置中心来读取
double fpp = 0.001 ;
// 调用反爬系统接口 拉取批量黑名单
List<String> blackIpList = getBlackIpList();
// 初始化 不隆过滤器
blackIpBloomFilter = BloomFilter.create(Funnels.stringFunnel(Charset.forName( "utf-8" )), blackIpList.size(), fpp);
for (String ip: blackIpList) {
blackIpBloomFilter.put(ip);
}
}
/**
* 判断是否是爬虫
*/
public boolean checkIpIsBlack(String ip) {
boolean contain = blackIpBloomFilter.mightContain(ip);
if (!contain) {
return false ;
}
// 不在redis白名单里面
if (!RedisUtils.exist(String.format( "whiteIp_%" , ip)){
return false ;
}
// 调用反爬系统接口 判断IP是否在黑名单里面
}
总结
上述只是列举了通过 IP 来反爬虫,这种反爬的话只能应对比较低级的爬虫,如果稍微高级一点的爬虫也可以通过代理 IP 来继续爬你的网站,这样的话成本可能就会加大了一点。爬虫虽然好,但是还是不要乱爬,“爬虫爬的好,牢饭吃到饱”
结束
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/69992959/viewspace-2751556/ ,如需转载,请注明出处,否则将追究法律责任。