您的位置:首页 > 其它

爬虫抓取网页相似度判断

2015-08-10 18:40 225 查看
爬虫抓取网页过程中,会产生很多的问题,当然最重要的一个问题就是重复问题,网页的重复抓取.最简单的方式就是对url去重.已经抓取过的url不再抓取.但是其实在实际业务中是需要对于已经抓取过的URL进行再次抓取的.例如 BBS .bbs存在大量的更新回复,但是url不会发生改变.

一般情况下的url去重方式,就是判断url是否抓取过,如果抓取过就不再抓取,或者是在一定时间内不再抓取..

我的需求也是这样的, 所以首先做的就是url去重. 在爬虫发现链接,加入待抓取队列的时候,会对url进行验证,是否抓取过,或者当前时间是否需要再次抓取该url. 如果在条件时间内抓取到的最终内容是否需要入库呢..?

因为业务问题:

1. DM 定时拿出数据库中的数据.

2. ETL需要定时拿出数据库中的数据.

但是他们都不会对数据进行判断是否之前已经做过处理. 所以也就会造成冗余,对于爬虫来说.每天抓取的数据至少百万级的,如果不进行去重判断,DM ETL 等其他所需数据的人员每天都要处理大量的已处理过数据.

很多人的处理方式都是 MD5 或者其他的一些加密方式. 甚至几种结合... 好吧,我就说说问题..




每次访问该网页都会增加次数,网站修改的内容仅仅为千分之一,一个字符... 但是对于md5来说,却是修改了整个世界..整个加密都改变了...




这是我记录的md5. 里面的内容为原文文章..文章修改的内容见上图..

经过测试.md5加密正文用于爬虫做去重判断.适用性很低...所以就修改了方案.

使用SimHash算法对于抓取数据加密.



import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;

public class SimHash {
    private String tokens;

    public BigInteger intSimHash;

    private String strSimHash;

    private int hashbits = 64;

    public SimHash(String tokens) {
        this.tokens = tokens;
        this.intSimHash = this.simHash();
    }

    public SimHash(String tokens, int hashbits) {
        this.tokens = tokens;
        this.hashbits = hashbits;
        this.intSimHash = this.simHash();
    }

    public BigInteger simHash() {
        // 定义特征向量/数组
        int[] v = new int[this.hashbits];
        // 修改为分词
        StringTokenizer stringTokenizer = new StringTokenizer(tokens);
        while (stringTokenizer.hasMoreTokens()) {
            String temp = stringTokenizer.nextToken(); // temp
            BigInteger t = this.hash(temp);
            for (int i = 0; i < this.hashbits; i++) {
                BigInteger bitmask = new BigInteger("1").shiftLeft(i);
                if (t.and(bitmask).signum() != 0) {
                    v[i] += 1;
                } else {
                    v[i] -= 1;
                }
            }
        }
        BigInteger fingerprint = new BigInteger("0");
        StringBuffer simHashBuffer = new StringBuffer();
        for (int i = 0; i < this.hashbits; i++) {
            // 4、最后对数组进行判断,大于0的记为1,小于等于0的记为0,得到一个 64bit 的数字指纹/签名.
            if (v[i] >= 0) {
                fingerprint = fingerprint.add(new BigInteger("1").shiftLeft(i));
                simHashBuffer.append("1");
            } else {
                simHashBuffer.append("0");
            }
        }
        this.strSimHash = simHashBuffer.toString();
        return fingerprint;
    }

    @SuppressWarnings({ "rawtypes", "unused", "unchecked" })
    public List subByDistance(SimHash simHash, int distance) {
        // 分成几组来检查
        int numEach = this.hashbits / (distance + 1);
        List characters = new ArrayList();
        StringBuffer buffer = new StringBuffer();
        int k = 0;
        for (int i = 0; i < this.intSimHash.bitLength(); i++) {
            // 当且仅当设置了指定的位时,返回 true
            boolean sr = simHash.intSimHash.testBit(i);

            if (sr) {
                buffer.append("1");
            } else {
                buffer.append("0");
            }

            if ((i + 1) % numEach == 0) {
                // 将二进制转为BigInteger
                BigInteger eachValue = new BigInteger(buffer.toString(), 2);
                buffer.delete(0, buffer.length());
                characters.add(eachValue);
            }
        }
        return characters;
    }

    public int getDistance(String str1, String str2) {
        int distance;
        if (str1.length() != str2.length()) {
            distance = -1;
        } else {
            distance = 0;
            for (int i = 0; i < str1.length(); i++) {
                if (str1.charAt(i) != str2.charAt(i)) {
                    distance++;
                }
            }
        }
        return distance;
    }

    /**
     * 计算Hash
     * 
     * @param source
     * @return
     */
    private BigInteger hash(String source) {
        if (source == null || source.length() == 0) {
            return new BigInteger("0");
        } else {
            char[] sourceArray = source.toCharArray();
            BigInteger x = BigInteger.valueOf(((long) sourceArray[0]) << 7);
            BigInteger m = new BigInteger("1000003");
            BigInteger mask = new BigInteger("2").pow(this.hashbits).subtract(
                    new BigInteger("1"));
            for (char item : sourceArray) {
                BigInteger temp = BigInteger.valueOf((long) item);
                x = x.multiply(m).xor(temp).and(mask);
            }
            x = x.xor(new BigInteger(String.valueOf(source.length())));
            if (x.equals(new BigInteger("-1"))) {
                x = new BigInteger("-2");
            }
            return x;
        }
    }

    /**
     * 计算海明距离
     * 
     * @param other
     * @return
     */
    public int hammingDistance(SimHash other) {
        BigInteger x = this.intSimHash.xor(other.intSimHash);
        int tot = 0;
        // 统计x中二进制位数为1的个数
        // 我们想想,一个二进制数减去1,那么,从最后那个1(包括那个1)后面的数字全都反了,对吧,然后,n&(n-1)就相当于把后面的数字清0,
        // 我们看n能做多少次这样的操作就OK了。
        while (x.signum() != 0) {
            tot += 1;
            x = x.and(x.subtract(new BigInteger("1")));
        }
        return tot;
    }

}

同样对于刚才的网站进行测试..

结果:




可以看出,对于网站进行简单的修改不会影响最终生成的hash值. 但是如果网站更新,会直接修改结果.适用于爬虫抓取去重更新. 所以建议使用该方式做去重方案
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: