Python-Scrapy 个人兴趣教程(三):扫尾

上一篇博文已经完成了代理IP抓取的核心部分,这一篇主要讲一下代理IP的检测。

所谓HTTP代理,检测方法很简单,就是用代理去请求一个网址,看看是否能够拿到正确回应。因为我们抓取IP是周期进行的,所以代理IP的验证也要不停的进行,简单说来就是一个队列的形式,抓取系统不停往队列里扔IP,检测进程不停取出IP进行检测,检测合格的IP放入另一个队列,不合格的直接丢弃。当然,因为代理IP的时效性,存放检测合格的代理IP的队列,自身也需要进行检测维护。


实现方法很简单,Python就是个工具库,利用urllib2的绑定代理功能,可以很简单的完成这个工作,稍微注意一点的就是和mongodb的交互,python上使用pymongo库和mongodb交互,API比较简单。为了提高检测效率,我选择了多线程模式。检测IP看做一个任务,这些任务完全可以并行进行,唯一需要注意的就是取IP和放IP。利用mongodb里一些线程安全的API(find_and _delete),可以避开线程锁的使用。(当然,我没有严格验证是否存在数据冲突,作为练手,这个实现方式足以,而且错误的代价基本忽略)


这个检测的进程不停读取IP队列里的数据,如果读不到了,则sleep一些时间,然后继续尝试读取。

这样一套方案搭建好之后,mongodb里时刻都存有当前30分钟内搜索到代理IP资源,这可以作为其他scrapy的准备资源,妈妈再也不用担心我的IP- -。。


我在测试代理IP的时候,把测试用的网页链接设置成了上一篇博客的地址http://blog.csdn.net/mingz_free/article/details/45967725。。。于是乎。。。。

这个博客的阅读量就飞起来了。。。我没有恶意刷点击量的意思。。求别封我的博客- -。。


附上部分源码:

scan.py

#! /usr/bin/env python
# -*- coding: utf-8 -*-
#

USAGE = """This is s special daemon used for checking whether the IP proxy works.
It can be managed by supervisor or some other tools, just make sure it works
all the time."""

import urllib
import urllib2
import httplib
import threading
import pymongo
import time
import sys
import os

from optparse import OptionParser

SETTING = {
        "SERVER":"localhost",
        "PORT":27017,
}

class IPPool(object):
    def __init__(self, server, port,  src_path, dst_path):
        connection = pymongo.MongoClient(server, port)
        self.src_collection = self._get_collection_by_path(connection, src_path)
        self.dst_collection = self._get_collection_by_path(connection, dst_path)

    def _get_collection_by_path(self, client, path):
        collection = client
        for node in path.split('.'):
            collection = getattr(collection, node)

        return collection

    def check_one(self, handler, url):
        res = self.src_collection.find_one_and_delete({})

        if res is None:
            time.sleep(360)
            return

        print "%s %s:%s, try to check" % (res["protocol"], res["ip"],
                res["port"])

        # Delete existed one
        self.dst_collection.delete_many(
                {"ip":res["ip"], "port":res["port"],
                "protocol":res["protocol"]})

        ret = handler(res["ip"], res["port"], res["protocol"], url)

        if ret:
            print "IP %s:%s good" % (res["ip"], res["port"])
            self.dst_collection.insert({"ip":res["ip"], "port":res["port"],
                "protocol":res["protocol"]})

    def get_one(self):
        return self.dst_collection.find_one()

    def get_all_iter(self):
        return self.dst_collection.find()


def proxy_test(ip, port='80', protocol='http', url='http://www.baidu.com'):
    proxy_url = protocol + '://' + ip + ':' + port
    proxy_support = urllib2.ProxyHandler({'http':proxy_url})
    opener = urllib2.build_opener(proxy_support, urllib2.HTTPHandler)
    request = urllib2.Request(url)
    request.add_header("Accept-Language", "zh-cn")
    request.add_header("Content-Type", "text/html; charset=gb2312")
    request.add_header("User-Agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2; .NET CLR 1.1.4322)")

    trycount = 1
    while trycount <= 2:
        try:
            f = opener.open(request, timeout=3.0)
            data = f.read()
            if 'baidu.com' in data:
                break
            else:
                return False
        except:
            time.sleep(3)
            trycount = trycount + 1

    if trycount > 2:
        return False

    return True


class CheckTask(threading.Thread):
    def __init__(self, pool, handler, url):
        threading.Thread.__init__(self)
        self.pool = pool
        self.handler = handler
        self.url = url


    def run(self):
        while 1:
            self.pool.check_one(self.handler, url)


class ClickTask(threading.Thread):
    def __init__(self, pool, handler, url):
        threading.Thread.__init__(self)
        self.pool = pool
        self.handler = handler
        self.url = url


    def run(self):
        while 1:
            for res in self.pool.get_all_iter():
                self.handler(res["ip"], res["port"], res["protocol"], self.url)
                print "Click %s by %s" % (self.url, res["ip"])



if __name__ == "__main__":
    pool1 = IPPool(SETTING["SERVER"], SETTING["PORT"],
            "proxy.unchecked", "proxy.checked")
    pool2 = IPPool(SETTING["SERVER"], SETTING["PORT"],
            "proxy.checked", "proxy.checked")

    parser = OptionParser(usage=USAGE,
            version='0.0.1')

    parser.add_option("-n", "--number", dest="number",
            default='20', metavar='NUMBER',
            help="The number of threads used to scan.")

    parser.add_option("-u", "--url", dest="url",
            default='http://www.baidu.com', metavar='URL',
            help="The url to used to scan.")

    parser.add_option("-c", "--click", dest="click",
            default=False, action="store_true",
            help="Click url by proxy IP")


    (options, args) = parser.parse_args()

    thread_number = int(options.number)
    print "Thread number is %d + %d" % (thread_number, 1)
    url = options.url

    if options.click:
        tasktype =ClickTask
    else:
        tasktype =CheckTask

    tasks = []

    for i in range(thread_number):
        tasks.append(tasktype(pool1, proxy_test, url))

    tasks.append(tasktype(pool2, proxy_test, url))

    for task in tasks:
        task.start()

    for task in tasks:
        task.join()

scan.py严格说起来只是一个死活不愿意退出的脚本,并不是一个daemon,这里我用supervisord来管理scan.py,把它变向作为一个daemon,即使出了什么差错退出了,还能自动重启。关于supervisor的使用,请自行google。


至此,这个练手的小方案就完成了,我时刻都有新鲜的代理IP使用啦。。大家有兴趣也可以做类似的事情,然后用这些代理IP绕开大部分网站对频繁抓取的限制。不过别高兴太早,数据抓取有很多问题需要攻克,绕开IP限制只是第一步,而且是很简单的一步,后面还会碰到很多具体问题,例如:身份验证,验证码,如何获取javascript生成的页面,如何处理大数据存储,如何提高服务器效率,额,后面的是进阶了。。。


有问题欢迎留言- -。。。不知道我还有没有兴致继续搞抓取了,下面会很忙。。。

你可能感兴趣的:(多线程,linux,网络,python)