scrapy---获取大量ip,异步写入,并多进程验证

我们在爬一些网页的过程中经常会碰到ip被封的情况,比如说拉勾网,这个时候就只能换ip。有些网站给我们提供了大量的免费ip(收费的也有,但是目前我只是自己随便写写,就没必要去买了),比如 http://www.xicidaili.com这里写图片描述
我们就可以自己去爬一些下来备用

1.获取免费ip

下面先放代码,这是一个静态网站,所以爬起来很简单也就不分析了

import scrapy
from xici.items import XiciItem


class xici(scrapy.Spider):
    name = "xici"

    def start_requests(self):
        for i in range(1, 16):
            yield scrapy.Request("http://www.xicidaili.com/wn/" + str(i), callback=self.parse)

    def parse(self, response):
        lis = response.xpath('//table[@id="ip_list"]//tr')
        for li in lis[1:]:
            item = XiciItem()
            item['ip'] = li.xpath('td[2]/text()').extract()
            item['post'] = li.xpath('td[3]/text()').extract()
            item['type'] = li.xpath('td[6]/text()').extract()
            item['speed'] = li.xpath('td[7]//@title').extract()
            yield item
            # url1=response.xpath('//*[@id="body"]/div[2]/a[11]/@href').extract()[0]
            # yield scrapy.Request("http://www.xicidaili.com"+url1,callback=self.parse)
2.异步写入数据库

说一下数据存储过程中的问题。同步写入数据库,速度实在太慢了,当爬取数据量很大的时候,会出现插入数据的速度跟不上网页的爬取解析速度,造成阻塞,为了解决这个问题需要将 MySQL 的数据存储异步化。Python 中提供了 Twisted 框架来实现异步操作,该框架提供了一个连接池,通过连接池可以实现数据插入 MySQL 的异步化。

from twisted.enterprise import adbapi
import MySQLdb.cursors

#这是同步写入数据库
'''class XiciPipeline(object):
    maorong_insert="""
                        insert into https(ip,post,type,speed)
                        values('{ip}','{post}','{type}','{speed}')
                    """

    def __init__(self,settings):
        self.settings=settings

    def process_item(self, item, spider):
        sqltext = self.maorong_insert.format(
            ip=pymysql.escape_string(item['ip'][0]),
            post=pymysql.escape_string(item['post'][0]),
            type=pymysql.escape_string(item['type'][0]),
            speed=pymysql.escape_string(item['speed'][0]),
        )
        self.cursor.execute(sqltext)
        return item

    @classmethod
    def from_crawler(cls, crawler):
        return cls(crawler.settings)

    def open_spider(self, spider):
        self.connect = pymysql.connect(
            host=self.settings.get('MYSQL_HOST'),
            port=self.settings.get('MYSQL_PORT'),
            db=self.settings.get('MYSQL_DBNAME'),
            user=self.settings.get('MYSQL_USER'),
            passwd=self.settings.get('MYSQL_PASSWD'),
            charset='utf8',
            use_unicode=True)

        # 通过cursor执行增删查改
        self.cursor = self.connect.cursor();
        self.connect.autocommit(True)

    def close_spider(self, spider):
        self.cursor.close()
        self.connect.close()'''

#这是异步写入数据库
class XiciTwistedPipeline(object):
    def __init__(self, dbpool):
        self.dbpool = dbpool

    # from_settings()函数是固定写法,该函数是用于读取settings.py配置信息的函数,第二个参数settings是一个字典。
    @classmethod
    def from_settings(cls, settings):
        args = dict(
                host='localhost',
                port=3306,
                db='xici',
                user='root',
                passwd='123456',
                charset='utf8',
                # 指定用用于创建cursor游标的类
                cursorclass=MySQLdb.cursors.DictCursor,
        )
        # 创建一个线程池对象
        # 参数1:用于连接MySQL数据库的驱动
        # 参数2:数据库的链接信息(host, port, user等)
        dbpool = adbapi.ConnectionPool("MySQLdb", **args)

        return cls(dbpool)

    def process_item(self, item, spider):
        # 在线程池dbpool中通过调用runInteraction()函数,来实现异步插入数据的操作。runInteraction()会insert_sql交由线程池中的某一个线程执行具体的插入操作。
        query = self.dbpool.runInteraction(self.insert, item)
        # addErrorback()数据库异步写入失败时,会执行addErrorback()内部的函数调用。
        query.addErrback(self.handler_error)

    def handler_error(self, failure):
        print('数据库插入数据失败:', failure)

    def insert(self, cursor, item):
        insert_sql = """
                        insert into https(ip,post,type,speed)
                        values(%s,%s,%s,%s)
                    """
        cursor.execute(insert_sql, (item['ip'], item['post'], item['type'], item['speed']))
        # 不需要执行commit()的操作了,会在线程池中自动指定提交的操作。

然后在settings.py里

ITEM_PIPELINES = {
    'xici.pipelines.XiciTwistedPipeline': 100,
    # 'xici.pipelines.XiciPipeline': 300,
}

当然也可以使用executemany写入,先把数据放到一个元组或列表里,然后每几千条写入一次。

3.多进程验证ip可用性

好了这只是把ip爬下来了,接下来还是验证一下这些ip能不能用(其实基本只有20%左右能用)。
其实验证一个ip基本需要1-3s,如果一个一个来的话,肯定不行的,所以这里又到了多进程的知识点

先放代码

#coding=utf-8

import pymysql as MySQLdb  #这里是python3  如果你是python2.x的话,import MySQLdb
import telnetlib
import time
import threading
from multiprocessing import Pool

host = '127.0.0.1'
user = 'root'
passwd = '111000'
port = 3306
db = 'xici'

def select_data(sql):
    try:
        conn = MySQLdb.connect(host=host,
                           port=port,
                           user=user,
                           passwd=passwd,
                           db=db,
                           charset='utf8', )
        cur = conn.cursor()
        cur.execute(sql)
        alldata = cur.fetchall()
        return alldata
    except Exception as e:
        pass
    finally:
        cur.close()
        conn.close()
        
#这就是验证能不能用的代码块
def test(rec):
    try:
        telnetlib.Telnet(rec[0], port=rec[1])
             
    #这里你也可以把ip和port放到一个元组里,然后用executemany更新数据库,或直接输出到文件
    except:
        print ("delete from https where ip='"+rec[0]+"'")
        #cur.execute("delete from https where ip='"+rec[0]+"'")
        #cur.execute("OPTIMIZE TABLE https")##
    else:
        print('sucess ip='+rec[0])
if __name__ == '__main__':
    sql = "select ip,post from https"
    alldata=select_data(sql)
    #这是单线程验证
    print('start single')
    temp1=[]
    sj1=time.time()
    for li in alldata:
        test(li,)
    print('single cost '+str(time.time()-sj1)+'s')
    #这是多进程验证
    print('start multiprocessing')
    pool=Pool()
    temp=[]
    sj2=time.time()
    for li in alldata:
        temp.append(pool.apply_async(test,args=(li,)))
    pool.close()
    pool.join()
    print('multiprocessing cost '+str(time.time()-sj2)+'s')

我一般只爬100个ip来用,基本验证下来有20个左右是可以用的,下面放一下单线程和多进程的时间区别。这是验证的300个ip
这里写图片描述
这里写图片描述
可以看到时间差别很大

4.扩展阅读

关于同步异步,多线程与多进程的理解可以看看这3篇文章,写的很通俗易懂
https://www.cnblogs.com/AsuraDong/p/threading_process.html
https://blog.csdn.net/liuhehe123/article/details/80925036
https://blog.csdn.net/sinat_22594309/article/details/53727084

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值