网络爬虫速成指南 (四) URL判重

 
如果采集量比较小:
布隆过滤器详解原理

如果采集量比较大:
redis:主要是把URL转为md5,作为key来进行判重

关于布隆过滤器器的参数说明,简单点说: private static BloomFilter<String> bloomFilter = new BloomFilter<String>(2X, X); 这样用就可以了
附:布隆过滤器实现,@author Magnus Skjegstad <magnus@skjegstad.com>
import java.io.Serializable; import java.nio.charset.Charset; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.BitSet; import java.util.Collection; /** * Implementation of a Bloom-filter, as described here: * http://en.wikipedia.org/wiki/Bloom_filter * * Inspired by the SimpleBloomFilter-class written by Ian Clarke. This * implementation provides a more evenly distributed Hash-function by using a * proper digest instead of the Java RNG. Many of the changes were proposed in * comments in his blog: * http://blog.locut.us/2008/01/12/a-decent-stand-alone-java * -bloom-filter-implementation/ * * @param <E> * Object type that is to be inserted into the Bloom filter, e.g. * String or Integer. * @author Magnus Skjegstad <magnus@skjegstad.com> */ public class BloomFilter<E> implements Serializable { private BitSet bitset; private int bitSetSize; private double bitsPerElement; private int expectedNumberOfFilterElements; // expected (maximum) number of // elements to be added private int numberOfAddedElements; // number of elements actually added to // the Bloom filter private int k; // number of hash functions static final Charset charset = Charset.forName("UTF-8"); // encoding used // for storing // hash values // as strings static final String hashName = "MD5"; // MD5 gives good enough accuracy in // most circumstances. Change to // SHA1 if it's needed static final MessageDigest digestFunction; static { // The digest method is reused between instances MessageDigest tmp; try { tmp = java.security.MessageDigest.getInstance(hashName); } catch (NoSuchAlgorithmException e) { tmp = null; } digestFunction = tmp; } /** * Constructs an empty Bloom filter. The total length of the Bloom filter * will be c*n. * * @param c * is the number of bits used per element. * @param n * is the expected number of elements the filter will contain. * @param k * is the number of hash functions used. */ public BloomFilter(double c, int n, int k) { this.expectedNumberOfFilterElements = n; this.k = k; this.bitsPerElement = c; this.bitSetSize = (int) Math.ceil(c * n); numberOfAddedElements = 0; this.bitset = new BitSet(bitSetSize); } /** * Constructs an empty Bloom filter. The optimal number of hash functions * (k) is estimated from the total size of the Bloom and the number of * expected elements. * * @param bitSetSize * defines how many bits should be used in total for the filter. * @param expectedNumberOElements * defines the maximum number of elements the filter is expected * to contain. */ public BloomFilter(int bitSetSize, int expectedNumberOElements) { this(bitSetSize / (double) expectedNumberOElements, expectedNumberOElements, (int) Math .round((bitSetSize / (double) expectedNumberOElements) * Math.log(2.0))); } /** * Constructs an empty Bloom filter with a given false positive probability. * The number of bits per element and the number of hash functions is * estimated to match the false positive probability. * * @param falsePositiveProbability * is the desired false positive probability. * @param expectedNumberOfElements * is the expected number of elements in the Bloom filter. */ public BloomFilter(double falsePositiveProbability, int expectedNumberOfElements) { this(Math.ceil(-(Math.log(falsePositiveProbability) / Math.log(2))) / Math.log(2), // c = k / ln(2) expectedNumberOfElements, (int) Math.ceil(-(Math .log(falsePositiveProbability) / Math.log(2)))); // k = // ceil // ( // - // log_2 // ( // false // prob // . // ) // ) } /** * Construct a new Bloom filter based on existing Bloom filter data. * * @param bitSetSize * defines how many bits should be used for the filter. * @param expectedNumberOfFilterElements * defines the maximum number of elements the filter is expected * to contain. * @param actualNumberOfFilterElements * specifies how many elements have been inserted into the * <code>filterData</code> BitSet. * @param filterData * a BitSet representing an existing Bloom filter. */ public BloomFilter(int bitSetSize, int expectedNumberOfFilterElements, int actualNumberOfFilterElements, BitSet filterData) { this(bitSetSize, expectedNumberOfFilterElements); this.bitset = filterData; this.numberOfAddedElements = actualNumberOfFilterElements; } /** * Generates a digest based on the contents of a String. * * @param val * specifies the input data. * @param charset * specifies the encoding of the input data. * @return digest as long. */ public static long createHash(String val, Charset charset) { return createHash(val.getBytes(charset)); } /** * Generates a digest based on the contents of a String. * * @param val * specifies the input data. The encoding is expected to be * UTF-8. * @return digest as long. */ public static long createHash(String val) { return createHash(val, charset); } /** * Generates a digest based on the contents of an array of bytes. * * @param data * specifies input data. * @return digest as long. */ public static long createHash(byte[] data) { long h = 0; byte[] res; synchronized (digestFunction) { res = digestFunction.digest(data); } for (int i = 0; i < 4; i++) { h <<= 8; h |= ((int) res[i]) & 0xFF; } return h; } /** * Compares the contents of two instances to see if they are equal. * * @param obj * is the object to compare to. * @return True if the contents of the objects are equal. */ @Override public boolean equals(Object obj) { if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } final BloomFilter<E> other = (BloomFilter<E>) obj; if (this.expectedNumberOfFilterElements != other.expectedNumberOfFilterElements) { return false; } if (this.k != other.k) { return false; } if (this.bitSetSize != other.bitSetSize) { return false; } if (this.bitset != other.bitset && (this.bitset == null || !this.bitset.equals(other.bitset))) { return false; } return true; } /** * Calculates a hash code for this class. * * @return hash code representing the contents of an instance of this class. */ @Override public int hashCode() { int hash = 7; hash = 61 * hash + (this.bitset != null ? this.bitset.hashCode() : 0); hash = 61 * hash + this.expectedNumberOfFilterElements; hash = 61 * hash + this.bitSetSize; hash = 61 * hash + this.k; return hash; } /** * Calculates the expected probability of false positives based on the * number of expected filter elements and the size of the Bloom filter. <br * /><br /> The value returned by this method is the <i>expected</i> rate of * false positives, assuming the number of inserted elements equals the * number of expected elements. If the number of elements in the Bloom * filter is less than the expected value, the true probability of false * positives will be lower. * * @return expected probability of false positives. */ public double expectedFalsePositiveProbability() { return getFalsePositiveProbability(expectedNumberOfFilterElements); } /** * Calculate the probability of a false positive given the specified number * of inserted elements. * * @param numberOfElements * number of inserted elements. * @return probability of a false positive. */ public double getFalsePositiveProbability(double numberOfElements) { // (1 - e^(-k * n / m)) ^ k return Math.pow((1 - Math.exp(-k * (double) numberOfElements / (double) bitSetSize)), k); } /** * Get the current probability of a false positive. The probability is * calculated from the size of the Bloom filter and the current number of * elements added to it. * * @return probability of false positives. */ public double getFalsePositiveProbability() { return getFalsePositiveProbability(numberOfAddedElements); } /** * Returns the value chosen for K.<br /> <br /> K is the optimal number of * hash functions based on the size of the Bloom filter and the expected * number of inserted elements. * * @return optimal k. */ public int getK() { return k; } /** * Sets all bits to false in the Bloom filter. */ public void clear() { bitset.clear(); numberOfAddedElements = 0; } /** * Adds an object to the Bloom filter. The output from the object's * toString() method is used as input to the hash functions. * * @param element * is an element to register in the Bloom filter. */ public void add(E element) { long hash; String valString = element.toString(); for (int x = 0; x < k; x++) { hash = createHash(valString + Integer.toString(x)); hash = hash % (long) bitSetSize; bitset.set(Math.abs((int) hash), true); } numberOfAddedElements++; } /** * Adds all elements from a Collection to the Bloom filter. * * @param c * Collection of elements. */ public void addAll(Collection<? extends E> c) { for (E element : c) add(element); } /** * Returns true if the element could have been inserted into the Bloom * filter. Use getFalsePositiveProbability() to calculate the probability of * this being correct. * * @param element * element to check. * @return true if the element could have been inserted into the Bloom * filter. */ public boolean contains(E element) { long hash; String valString = element.toString(); for (int x = 0; x < k; x++) { hash = createHash(valString + Integer.toString(x)); hash = hash % (long) bitSetSize; if (!bitset.get(Math.abs((int) hash))) return false; } return true; } /** * Returns true if all the elements of a Collection could have been inserted * into the Bloom filter. Use getFalsePositiveProbability() to calculate the * probability of this being correct. * * @param c * elements to check. * @return true if all the elements in c could have been inserted into the * Bloom filter. */ public boolean containsAll(Collection<? extends E> c) { for (E element : c) if (!contains(element)) return false; return true; } /** * Read a single bit from the Bloom filter. * * @param bit * the bit to read. * @return true if the bit is set, false if it is not. */ public boolean getBit(int bit) { return bitset.get(bit); } /** * Set a single bit in the Bloom filter. * * @param bit * is the bit to set. * @param value * If true, the bit is set. If false, the bit is cleared. */ public void setBit(int bit, boolean value) { bitset.set(bit, value); } /** * Return the bit set used to store the Bloom filter. * * @return bit set representing the Bloom filter. */ public BitSet getBitSet() { return bitset; } /** * Returns the number of bits in the Bloom filter. Use count() to retrieve * the number of inserted elements. * * @return the size of the bitset used by the Bloom filter. */ public int size() { return this.bitSetSize; } /** * Returns the number of elements added to the Bloom filter after it was * constructed or after clear() was called. * * @return number of elements added to the Bloom filter. */ public int count() { return this.numberOfAddedElements; } /** * Returns the expected number of elements to be inserted into the filter. * This value is the same value as the one passed to the constructor. * * @return expected number of elements. */ public int getExpectedNumberOfElements() { return expectedNumberOfFilterElements; } /** * Get expected number of bits per element when the Bloom filter is full. * This value is set by the constructor when the Bloom filter is created. * See also getBitsPerElement(). * * @return expected number of bits per element. */ public double getExpectedBitsPerElement() { return this.bitsPerElement; } /** * Get actual number of bits per element based on the number of elements * that have currently been inserted and the length of the Bloom filter. See * also getExpectedBitsPerElement(). * * @return number of bits per element. */ public double getBitsPerElement() { return this.bitSetSize / (double) numberOfAddedElements; } }

 

package com.lietu.show;

import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Properties;

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

//提取商品信息
public class ExtractProduct {

    static BloomFilter<String> urlSeen = new BloomFilter<String>(200000, 20000);

    /**
     * @param args
     * @throws IOException
     */
    public static void main(String[] args) throws IOException {
        String url = "http://www.xiu.com/about/classifymap.shtml";
        Document doc = Jsoup.connect(url).get();
        Elements links = doc.select("a[href]"); // 带有href属性的a标签
        for (Element link : links) { // 遍历每个链接
            String linkHref = link.attr("href"); // 得到href属性中的值,也就是url地址
            if (linkHref.startsWith("http://list.xiu.com/")
                    || linkHref.startsWith("http://brand.xiu.com/")) {
                linkHref = getListURL(linkHref);
                if (urlSeen.contains(linkHref))
                    continue;
                // System.out.println(linkHref); //输出url地址和锚点上的文字说明
                extractList(linkHref);
            }
        }
    }
    
    public static String getListURL(String oldURL){
        ParseURL splitURL = new ParseURL(oldURL);
        if(splitURL.searchparms==null){
            return splitURL.baseURL;
        }
        //System.out.println(splitURL.baseURL);
        String curPage = splitURL.searchparms.get("currentPage");
        // http://list.xiu.com/20069.html?currentPage=2
        if (curPage != null)
            //System.out.println(splitURL.baseURL + "?currentPage=" + curPage);
            return splitURL.baseURL + "?currentPage=" + curPage;
        else{
            //System.out.println(splitURL.baseURL);
            return splitURL.baseURL;
        }
    }

    //处理目录页
    // http://list.xiu.com/19757.html
    // http://brand.xiu.com/3117.html
    public static void extractList(String url) {
        System.out.println("List url:" + url);
        urlSeen.add(url);
        Document doc = null;
        try {
            doc = Jsoup.connect(url).get();
        } catch (Exception e) {
            try {
                doc = Jsoup.connect(url).get();
            } catch (IOException e1) {
                // TODO Auto-generated catch block
                e1.printStackTrace();
                return;
            }
        }

        Elements links = doc.select("a[href]"); // 带有href属性的a标签
        for (Element link : links) { // 遍历每个链接
            String linkHref = link.attr("href"); // 得到href属性中的值,也就是url地址
            if (linkHref.startsWith("http://list.xiu.com/")
                    || linkHref.startsWith("http://brand.xiu.com/")) {
                linkHref = getListURL(linkHref);
                if (urlSeen.contains(linkHref))
                    continue;
                extractList(linkHref); // 输出url地址和锚点上的文字说明
            } else if (linkHref.startsWith("http://item.xiu.com/product/")) { // "http://item.xiu.com/product/0359097.shtml"
                if (urlSeen.contains(linkHref))
                    continue;
                getProduct(linkHref);
            }
        }
    }

    //处理详细页
    public static void getProduct(String url) {
        System.out.println("Product url:" + url);
        urlSeen.add(url);
        // String url = "http://item.xiu.com/product/0359097.shtml";
        Document doc = null;
        try {
            doc = Jsoup.connect(url).get();
        } catch (Exception e) {
            try{
                doc = Jsoup.connect(url).get();
            }
            catch (Exception e2) {
                try {
                    doc = Jsoup.connect(url).get();
                } catch (IOException e1) {
                    // TODO Auto-generated catch block
                    e1.printStackTrace();
                    return;
                }
            }
        }
        Elements links = doc.select("div.p_title"); // 带有href属性的a标签
        String name = links.get(0).childNode(1).childNode(0).toString();
        // System.out.println(name);

        Element link = doc.select("div.conlist").get(2);
        String desc = link.text();
        // System.out.println(link.text());

        Element img = doc.select("#imgPic").first();
        String imgSrc = img.attr("src");
        // System.out.println(img.attr("src"));
        
        Element thumb = doc.select("img[onload]").first();
        String thumbSrc = thumb.attr("src");
        //System.out.println(thumbSrc);
        
        insertDatabase(url,name,desc,imgSrc,thumbSrc);
    }

    public static void insertDatabase(String url, String name, String desc,
            String img,String thumbSrc) {
        Connection conn = null;
        PreparedStatement stmt2 = null;
        try {
            Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
            Properties p = new Properties(); 
            p.put("charSet", "gb2312");
            conn = DriverManager
                    .getConnection("jdbc:odbc:driver={Microsoft Access Driver (*.mdb)};DBQ=good.mdb",p);

            stmt2 = conn
                    .prepareStatement("insert into good(url,productName,productDesc,img,thumb)values(?,?,?,?,?)");
            stmt2.setString(1, url);
            stmt2.setString(2, name);
            stmt2.setString(3, desc);
            stmt2.setString(4, img);
            stmt2.setString(5, thumbSrc);
            stmt2.executeUpdate();
        } catch (Exception ex) {
            ex.printStackTrace();
        } finally {
            try {
                if (stmt2 != null)
                    stmt2.close();
                if (conn != null)
                    conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

}

 

  给定误算率p,插入元素个数n,那么最优位数组大小为:

 

Hash函数个数选取最优数目为:

  


python 实现:

import math

N = 30000000000.0
P = 0.001

def get_len_arr(n, p):
    return -(n * math.log(p) / (math.log(2) ** 2))


def get_k(p):
    return - (math.log(p) / math.log(2))

m = get_len_arr(N, P)

print m, 'bit'

g = m / (8 * 1024 * 1024 * 1024)

print g, 'GB'

k = get_k(P)

print k, 'ge hash hanshu'
=========================
4.31327626982e+11 bit
50.2131445079 GB 300亿,50G内存。
9.96578428466 ge hash hanshu

 

 

posted on 2012-12-14 15:39  雨渐渐  阅读(1002)  评论(0编辑  收藏  举报

导航