【精】反爬虫技术研究

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/fenglixiong123/article/details/85112435

一、背景

       

              网络爬虫(又被称为网页蜘蛛,网络机器人,在FOAF社区中间,更经常的称为网页追逐者),是一种按照一定的规则,自动地抓取万维网信息的程序或者脚本,它们被广泛用于互联网搜索引擎或其他类似网站,

可以自动采集所有其能够访问到的页面内容,以获取或更新这些网站的内容和检索方式。从功能上来讲,爬虫一般分为数据采集,处理,储存三个部分。传统爬虫从一个或若干初始网页的URL开始,获得初始网页上的URL,

在抓取网页的过程中,不断从当前页面上抽取新的URL放入队列,直到满足系统的一定停止条件。

爬虫的危害?(为什么要反爬虫)

             1.爬虫技术造成的大量IP访问网站侵占带宽资源

             2.恶意爬虫会对服务器发起Dos攻击

             3.爬虫拖垮了站点,严重影响了用户体验。

             4.公司的重要资源被批量爬取,丧失竞争力。

             5.用户隐私的泄露

             6.侵犯知识产权

最常见的危害:由于爬虫会造成大量的访问请求以至于拖垮服务器,所以反爬虫是每一个网站必须要严肃考虑的一个问题,由于扬州项目涉及宝贵的房源信息数据内容,很容易被其他竞争对手爬取其数据内容

而标准版中,没有涉及到爬虫相关问题,故此需要进行反爬虫的研究来完善标准版项目。

二、目标

我们知道爬虫和反爬虫总是相对的,有了新的爬虫技术就会出现新的反爬虫技术,有了新的反爬虫技术就会再出现新的反爬虫技术如此往复,所以我们要做的:

1.首先规避掉一些简单粗暴的爬虫

2.尽可能增加爬虫的爬取难度

3.加大爬取得信息解析难度

4.重要信息限制访问

总之让市面上大部分爬虫望而却步。

四、实现方式

通过查阅资料总结了网上主要的爬虫思路,也研究了各种反爬虫策略,将比较有用的爬虫技术实现方式分享出来:

序号

名称

简介

实现方案

优点

缺点

反爬等级

备注说明

1 ID连续性问题 由于大多数数据表会使用数据库主键自动生成机制,这样会导致爬虫程序按照有序的ID自己生成ID来爬取数据 如可以自定义生成随机主键方案,或者使用UUID 避免爬虫按ID规律顺序抓取内容 ID不连续在查找定位问题时候可能没那么方便,但是无伤大局 ★★★★ 建议使用
2 robots.txt配置 这个文件来告诉搜索机器人不要爬行我们的部分网页,俗称君子协议

例如禁止360爬虫禁止爬取我们的一些文件夹

User-agent: 360spider
Disallow: /admin/ 后台管理文件
Disallow: /require/ 程序文件
Disallow: /attachment/ 附件
Disallow: /images/ 图片
Disallow: /data/ 数据库文件

可以告诉那些知名网站搜索引擎等不要爬取自己不想被爬取得内容 由于非强制的,可以遵守也可以不遵守,这就导致很多爬虫私下并不遵守这个协议,还是会非法爬取内容,限制性比较弱  
3 User-Agent检测 无论是浏览器还是爬虫程序,在向服务器发起网络请求的时候,都会发过去一个头文件:headers,这里面的大多数的字段都是浏览器向服务器”表明身份“用的,对于爬虫程序来说,最需要注意的字段就是:User-Agent
很多网站都会建立 user-agent白名单,只有属于正常范围的user-agent才能够正常访问。

采用拦截器拦截到每一个请求,取得Header,拿到User-Agent

(1)白名单法:首先设置自己的白名单用户代理集,包括市面上主流的代理,抓到请求的UA进行对比,如果包含,则通过,否则请求不予通过

(2)黑名单法:python、java、php这些程序爬虫会携带这些信息,由于白名单难以完全穷举市面所有UA,所以可以采用黑名单法,禁止包含这些的请求通过

可以过滤一小部分简单粗暴的爬虫 大多数爬虫都会伪造这些UA,所以此法也会经常失效  
4 有限数据访问 对用户不会任意返回所有的数据,比如只给半年的数据权限 调用接口请求数据时候只返回限制页数比如50页数据或者只有半年数据允许访问,超过阈值则禁止访问更多数据,像微博的粉丝不允许看到全部粉丝只能看到一部分 爬虫爬不到全部数据有可能就放弃,很好的保证数据安全性 很多网站不适用,需要全量数据访问 ★★★  
5 访问频率限制 对规定时间访问过多的用户封禁IP地址 在redis中以访问IP为key,访问次数为value,设置过期时间,达到阈值,则封禁用户或者跳出验证码都可以 保护服务器防止压力过大 容易误伤普通用户 ★★★ 代码示例仅供参考
6 JS动态加载 对价格手机号等敏感信息懒加载 设置按钮,需要用户需要点击才能显示 保护重要信息不被爬虫直接获取 用户体验差点 ★★★★  
7 cookie/token等有效时间 限制登录用户时间 对cookie或者token设置过期时间爬虫一般会一直在线不断爬取,这种需要强制其下线,重新登录 加大爬虫爬取难度 用户体验差点 ★★★  
8 账户进行访问控制 每个用户拥有不通权限,爬虫一般只有普通用户的权限,不通用户看到的信息量不一样 对不通用户访问数据权限不一样,比如管理员可以访问全部内容,但是普通用户只能看到自己那部分,游客只能看到几条数据,像微信的朋友圈陌生人只能看到10条内容,自己也可以设置半年可见,则爬虫获取不到你的全量信息 可以很好的隐藏宝贵数据 很多网站不适用,比如信息网的房源数据普通游客也是能看得到的 ★★  
9 ip黑名单 对一些已知的爬虫封禁其IP地址 建立IP和名单表,设置拦截器请求到来时候首先进行IP解析然后和黑名单池做对比,一旦发现属于黑名单禁止访问,对于后面发现的不正常访问的地址也进行加入黑名单 可以封禁一些顽固爬虫的访问 现在网上很多IP代理池,爬虫会不断的换IP地址进行爬取 ★★★  
10 图片水印 对网站图片加水印如:扬州信息网 在图片保存之前加上水印

爬虫即使爬到信息也不能直接使用,增加爬虫门槛

政府部门不一定同意增加水印 ★★★★★ 代码示例仅供参考
11 验证码 访问重要信息需要输入验证码 访问敏感信息时候弹出验证码窗口,需要用户输入一个表达式计算结果正确才能够继续访问调取接口数据 爬虫一般难以应对验证码这些技术 用户体验不是很好 ★★★★★  
12 信息图片化 对价格以及手机号采用图片显示方式

存储手机号之前将手机号生成图片存储在图片服务器然后返回key存储在数据库,
返回给用户时候返回一个key

这部分信息图片显示爬虫即使爬到也难以解析,这样可以让其不再爬取 界面用户体验不是很好 ★★★★  
13 接口加密 后台返回的是加密数据 后端采用加密算法如对称加密或者非对称加密算法,md5,sha等,前端对数据进行解析 这样即使爬虫拿到链接得到的数据也是加密过的数据,难以解析 前后台工作量比较大 ★★★★  
14 自定义字体 页面显示896但是查看网页源码确是325,效果:页面表现效果和网页源码对不上 比如价格手机号信息数字0123456789可以在前端封装一套字体为3659842301等,后端根据相同规则返回数据,前端直接用自定义字体库进行显示 增加了爬虫解析难度,甚至难以捉摸其规律,还可以定期变更规律 可能执行效率低点 ★★★★★  
15 信息混淆策略 页面显示的数据查看源代码是乱码数据 可以采用css3的自定义字体前端配合来做 增加页面解析难度 页面复杂化 ★★★★★  

五、使用方式

          这么多反爬虫方案,针对不同的项目,遇到的问题和需求也不一样,所能采用的反爬虫方案也不一样,那么如何来在标准版统一使用这个方案呢?

              可以对这些反爬虫方案按照反爬力度级别排序,然后逐一实现,可以做成可配置方式,或者开关方式

              针对不同项目只需要简单配置既可以使用合适的反爬虫方案

六、备注说明

限制访问频率:

访问频率限制

@Retention(RetentionPolicy.RUNTIME)

@Target(ElementType.METHOD)

@Documented

//最高优先级

@Order(Ordered.HIGHEST_PRECEDENCE)

public @interface RequestLimit {

    /**

     *

     * 允许访问的次数,默认值MAX_VALUE

     */

    int count() default Integer.MAX_VALUE;

    /**

     *

     * 时间段,单位为毫秒,默认值一分钟

     */

    long time() default 60000;

}

@Aspect

@Component

public class RequestLimitContract {

    private static final Logger logger = LoggerFactory.getLogger("RequestLimitLogger");

    @Autowired

    private RedisTemplate<String, String> redisTemplate;

    @Before("within(@org.springframework.stereotype.Controller *) && @annotation(limit)")

    public void requestLimit(final JoinPoint joinPoint, RequestLimit limit) throws RequestLimitException {

      try {

            Object[] args = joinPoint.getArgs();

            HttpServletRequest request = null;

            for (int i = 0; i < args.length; i++) {

                if (args[i] instanceof HttpServletRequest) {

                    request = (HttpServletRequest) args[i];

                    break;

                }

            }

            if (request == null) {

                throw new RequestLimitException("方法中缺失HttpServletRequest参数");

            }

            String ip = HttpRequestUtil.getIpAddr(request);

            String url = request.getRequestURL().toString();

            String key = "req_limit_".concat(url).concat(ip);

            long count = redisTemplate.opsForValue().increment(key, 1);

            if (count == 1) {

                redisTemplate.expire(key, limit.time(), TimeUnit.MILLISECONDS);

            }

            if (count > limit.count()) {

                logger.info("用户IP[" + ip + "]访问地址[" + url + "]超过了限定的次数[" + limit.count() + "]");

                throw new RequestLimitException();

            }

        catch (RequestLimitException e) {

            throw e;

        catch (Exception e) {

            logger.error("发生异常: ", e);

        }

    }

}

图片加水印方法:

图片水印

/**

     * @param srcImgPath 源图片路径

     * @param tarImgPath 保存的图片路径

     * @param waterMarkContent 水印内容

     * @param markContentColor 水印颜色

     * @param font 水印字体

     */

    public void addWaterMark(String srcImgPath, String tarImgPath, String waterMarkContent,Color markContentColor,Font font) {

        try {

            // 读取原图片信息

            File srcImgFile = new File(srcImgPath);//得到文件

            Image srcImg = ImageIO.read(srcImgFile);//文件转化为图片

            int srcImgWidth = srcImg.getWidth(null);//获取图片的宽

            int srcImgHeight = srcImg.getHeight(null);//获取图片的高

            // 加水印

            BufferedImage bufImg = new BufferedImage(srcImgWidth, srcImgHeight, BufferedImage.TYPE_INT_RGB);

            Graphics2D g = bufImg.createGraphics();

            g.drawImage(srcImg, 00, srcImgWidth, srcImgHeight, null);

            g.setColor(markContentColor); //根据图片的背景设置水印颜色

            g.setFont(font);              //设置字体

            //设置水印的坐标

            int x = srcImgWidth - 2*getWatermarkLength(waterMarkContent, g); 

            int y = srcImgHeight - 2*getWatermarkLength(waterMarkContent, g); 

            g.drawString(waterMarkContent, x, y);  //画出水印

            g.dispose(); 

            // 输出图片 

            FileOutputStream outImgStream = new FileOutputStream(tarImgPath); 

            ImageIO.write(bufImg, "jpg", outImgStream);

            System.out.println("添加水印完成"); 

            outImgStream.flush(); 

            outImgStream.close(); 

        catch (Exception e) {

            // TODO: handle exception

        }

    }

    public int getWatermarkLength(String waterMarkContent, Graphics2D g) { 

        return g.getFontMetrics(g.getFont()).charsWidth(waterMarkContent.toCharArray(), 0, waterMarkContent.length()); 

    

    public static void main(String[] args) {

        Font font = new Font("微软雅黑", Font.PLAIN, 35);                     //水印字体

        String srcImgPath="H:/安静时写写/写写博客/Java实现给图片添加水印/s.jpg"//源图片地址

        String tarImgPath="H:/安静时写写/写写博客/Java实现给图片添加水印/t.jpg"//待存储的地址

        String waterMarkContent="图片来源:http://blog.csdn.net/zjq_1314520";  //水印内容

        Color color=new Color(255,255,255,128);                               //水印图片色彩以及透明度

        new WaterMarkUtils().addWaterMark(srcImgPath, tarImgPath, color, waterMarkContent,font);

    }

猜你喜欢

转载自blog.csdn.net/fenglixiong123/article/details/85112435
今日推荐