我们在爬一些网页的过程中经常会碰到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