爬虫笔记(11)性能问题

1.

过年吧从未啥事干,继续捣鼓爬虫。开始是准备爬豆瓣电影之,豆瓣存在一些反爬机制,爬一会就爬不动了。当然后面是突破了这界定,使用随机bid,设置cookie。据说见面面世验证码,我爬了几万总统影片也绝非起这问题。初期的想法是采用代理ip,网络及的免费代办ip大都不指谱,懒得捣鼓。
在豆瓣电影是爬虫中,我骨子里是使简单单步骤来执行。第一有些是按部就班年标签查找电影,从1900到2017年,将每个电影链接存入到mongodb中,然后开展去再(手动)。

class DoubanMovies(scrapy.Spider):
    name = "douban"
    prefix = 'https://movie.douban.com/tag/'
    start_urls = []
    for i in range(1900,2018):
        start_urls.append(prefix+str(i))

    def parse(self,response):
        names = response.xpath('//div[@class="pl2"]/a/text()').extract()
        names = [name.strip('\n/ ') for name in names]
        names = [name for name in names if len(name)>0] #去掉空名字
        movie_urls = response.xpath('//div[@class="pl2"]/a/@href').extract()
        hrefs = response.xpath('//div[@class="paginator"]/a/@href').extract()#获取分页链接
        for href in hrefs:
           yield scrapy.Request(href, callback=self.parse)
        for i in range(len(names)):
            yield {'name':names[i],'url':movie_urls[i]}

有关mongodb去还之问题,我下的临时表。主要是本人对mongodb确实无熟识,而且我本着JavaScript这样的语法着实不感冒。下面的代码很简单,每个电影链接就是是https://movie.douban.com/subject/26685451/
,我这里特别将及时当中的数字提取出,然后进行对比,这个得是绝无仅有的。distinct会将获得的数目更的拓展联合,这当sql中也产生应声功能。

nums = movies.distinct("number")#我把链接中的数字提取了出来
m = db.movies1
for num in mums:
  movie = movies.find_one({"number":num})
  m.insert_one(movie)
moives.drop()#删除原来的数据表
m.rename('movie')#把新表命名

尚生个问题虽是对douban的时间限制,需要采取DOWNLOAD_DELAY安装时间间隔,当然我使用bid突破了是范围。
下是第二只爬虫的代码,这个代码就是造访每个电影页面提取相关数据,然后存到mongodb中。数据提取没什么难度,为了迅速判断xpath有效,最好开端一个scrapy
shell进行测试。

class DoubanMovies(scrapy.Spider):
    name = "doubansubject"
    def start_requests(self):
        MONGO_URI = 'mongodb://localhost/'
        client = MongoClient(MONGO_URI)
        db = client.douban
        movie = db.movie
        cursor = movie.find({})
        urls = [c['url'] for c in cursor]
        for url in urls:
            bid = "".join(random.sample(string.ascii_letters + string.digits, 11))
            yield scrapy.Request(url,callback=self.parse,cookies={"bid":bid})

def parse(self,response):

    title = response.xpath('//span[@property="v:itemreviewed"]/text()').extract_first()
    year = response.xpath('//span[@class="year"]/text()').extract_first()#(2016)
    pattern_y = re.compile(r'[0-9]+')
    year = pattern_y.findall(year)
    if len(year)>0:
        year = year[0]
    else:
        year = ""

    directors = response.xpath('//a[@rel="v:directedBy"]/text()').extract()#导演?有没有可能有多个导演呢
    '''
    评分人数
    '''
    votes= response.xpath('//span[@property="v:votes"]/text()').extract_first()#评分人数
    '''
    分数
    '''
    score = response.xpath('//strong[@property="v:average"]/text()').extract_first()#抓取分数
    #编剧不好找等会弄
    '''
    演员
    '''
    actors = response.xpath('//a[@rel="v:starring"]/text()').extract()#演员
    genres = response.xpath('//span[@property="v:genre"]/text()').extract()#电影类型
    html = response.body.decode('utf-8')
    pattern_zp = re.compile(r'制片国家/地区:(.*)<br/>')
    nations = pattern_zp.findall(html)
    if len(nations)>0 :
        nations = nations[0]
        nations = nations.split('/')
        nations = [n.strip() for n in nations]
    '''
    多个国家之间以/分开,前后可能出现空格也要删除
    '''
    pattern_bj = re.compile(r"编剧: (.*)<br/>")
    bj_as = pattern_bj.findall(html)
    '''
    bj_as 内容是
    [<a>编剧</a>,<a></a>,<a></a>,<a></a>,]
    需要进一步提取
    '''
    p = re.compile(r'>(.*)<')
    bj = [p.findall(bj) for bj in bj_as]
    '''
    p.findall也会产生数组,需要去掉括号,只有有数据才能去掉
    '''
    bj = [b[0].strip() for b in bj if len(b)>0]#编剧的最终结果

    '''
    语言
    语言: 英语 / 捷克语 / 乌克兰语 / 法语<br/>
    '''
    pattern_lang = re.compile(r'语言:(.*)<br/>')
    langs = pattern_lang.findall(html)
    if len(langs)>0:
        langs = langs[0]
        langs = langs.split('/')
        langs = [l.strip() for l in langs]
    runtime = response.xpath('//span[@property="v:runtime"]/@content').extract_first()
    '''
    上映日期也有多个
    '''
    releasedates = response.xpath('//span[@property="v:initialReleaseDate"]/text()').extract()
    '''
    标签
    '''
    tags = response.xpath('//div[@class="tags-body"]/a/text()').extract()

    ##这里不能用return
    yield {"title":title,"year":year,"directors":directors,"score":score,"votes":votes,
    "actors":actors,"genres":genres,"nations":nations,"bj":bj,"langs":langs,"runtime":runtime,
    "releasedates":releasedates,"url":response.url
    }

面的代码确实会健康干活,但是发生只缺陷就是是最慢,不顶五万个页面就要几单小时,显然瓶颈在解析这等同块。性能问题会见当底下一个例子中讨论。

原创文章,转载请注明出处:http://jameswxx.iteye.com/blog/2093785

2

性问题的确是个可怜题目,在满足能爬取的动静下,速度而优化。这几乎上抓取一个AV网站,没错AV网站的子文件。先捉到手文章列表,再拘捕到手每个详细页面,访问种子下载页面,最后下载里面的种文件。

  • 方法一
    本条代码很简单利用的凡requests来下充斥文件,里面的下载功能代码就是打requests教程被拷贝出来的。

def process_item(self, item, spider):
        try:
            bt_urls = item['bt_urls']
            if not os.path.exists(self.dir_path):
                os.makedirs(self.dir_path)
            '''
            检查文件夹是否存在这段代码应该放到open_spider中去才是合适的,启动检查一下后面就不管了
            '''
            for url in bt_urls:
                response = requests.get(url,stream=True)
                attachment = response.headers['Content-Disposition']
                pattern = re.compile(r'filename="(.+)"')
                filename = pattern.findall(attachment)[0]
                filepath = '%s/%s' % (self.dir_path,filename)
                with open(filepath, 'wb') as handle:
                    #response = requests.get(image_url, stream=True)
                    for block in response.iter_content(1024):
                        if not block:
                            break
                        handle.write(block)
                '''
                整个代码肯定会严重影响爬虫的运行速度,是否考虑多进程方法
                '''
        except Exception as e:
            print(e)
        return item

bt_url种子的链接就是置身bt_urls,由于是自从下载页面返回的item,实际中尽多单出一个链接。这个代码运行起来没什么问题,但是速度相当迟缓。scrapy使用的凡异步网络框架,但是requests是实的协同方法,单线程的情况下一定影响到所有体系的执行。必须要突破这个瓶颈,实际中如果先考虑代码能是运行更考虑任何地方。

  • 方法二
    既然在本线程中一直生充斥会造成线程阻塞,那被一个新的进程如何。

class DownloadBittorrent2(object):

    def __init__(self, dir_path):
        self.dir_path = dir_path
        # self.mongo_db = mongo_db

    @classmethod
    def from_crawler(cls, crawler):
        return cls(
            dir_path =crawler.settings.get('DIR_PATH'),
        )

    def open_spider(self, spider):
        if not os.path.exists(self.dir_path):
            os.makedirs(self.dir_path)

    def close_spider(self, spider):
        pass

    def downloadprocess(self,url):
        try:
            response = requests.get(url,stream=True)
            attachment = response.headers['Content-Disposition']
            pattern = re.compile(r'filename="(.+)"')
            filename = pattern.findall(str(attachment))[0]#这里attachment是bytes必须要转化
            filepath = '%s/%s' % (self.dir_path,filename)
            with open(filepath, 'wb') as handle:
                #response = requests.get(image_url, stream=True)
                for block in response.iter_content(1024):
                    if not block:
                        break
                    handle.write(block)
        except Exception as e:
            print(e)
    def process_item(self, item, spider):

        bt_urls = item['bt_urls']
        if len(bt_urls)>0:#最多只有一个url
            p = Process(target=self.downloadprocess,args=(bt_urls[0],))
            p.start()

        return item

其一代码也能够健康办事,但是报错,直接促成服务器挂了。
HTTPConnectionPool(host='taohuabbs.info', port=80): Max retries exceeded with url: /forum.php?mod=attachment&aid=MjAxNzk 0fDA0OTkxZjM0fDE0ODU4NjY0OTZ8MHwxMzMzNDA= (Caused by NewConnectionError('<requests.packages.urllib3.connection.HTTPConne ction object at 0x00000000041F9048>: Failed to establish a new connection: [WinError 10060]鉴于连接方在一段时间后尚未正确回答或连的主机没有反应,连接尝试失败。’,))
其一也许同设置的延产生关系(我就无装延迟),反正就是把服务器将死了。还有就是是requests在这种异常情况下容错能力来题目。

  • 方法三
    既然scrapy自带了一个Filespipeline,那么是未是可考虑就此者来下充斥也!可以试!

class DownloadBittorrent3(FilesPipeline):
    def get_media_requests(self, item, info):
        for file_url in item['bt_urls']:
            yield scrapy.Request(file_url)

代码报错了,原因是文本称由不上马。这个就是关乎到何以命名下载文件称之题材。如果链接中带动*.jpg这样类似的名,程序不见面发出问题,如果非是碰头咋样,链接中或许出现操作系统不同意以文书称受冒出的字符,这即会报错。我对系统自带的斯pipeline了解很少,就无继承研究。
还有少数我要文件称来于服务器的申报,对于下载文件服务器可能会见管文件名发过来,这个就算以headers的Content-Disposition字段中。也就算是是说自家不能不要优先走访网络之后才会确定文件称。

  • 方法四
    前方我们还动了pipeline来拍卖,实际上我们全好绝不pipeline而直白以spider中处理。

    def download(self,response):
        '''
        在爬取过程中发现有可能返回不是torrent文件,这时候要考虑容错性问题,虽然爬虫并不会挂掉
        '''
        attachment = response.headers['Content-Disposition']
        pattern = re.compile(r'filename="(.+)"')
        filename = pattern.findall(attachment.decode('utf-8'))[0]
        filepath = '%s/%s' % (self.settings['DIR_PATH'],filename)
        with open(filepath, 'wb') as handle:
            handle.write(response.body)

这种办法性能是,对比前面50/min速度,这个好上100/min。其实我们好更快。

 

3

于其实的下载中,我们若充分利用scrapy的大网下载框架,这个特性好容错性高,而且可排错。上面的10060错误,我估摸在http中恐怕就是是503(服务器无法到)。
前方的点子还当单线程中运作,虽然后面来差不多进程版的下载代码,由于没scrapy稳定所以我着想就此几近只爬虫来贯彻。如果开行两独scrapy爬虫,一个顶住爬页面,一个顶住下载,这样的频率应会愈不丢。虽然眼前的记中出关联相关代码,使用redis来贯彻分布式。当然在单机上称不齐分布式,但是采取redis作为进程之中通讯手段确实尽好的,不管是大半进程要分布式都能怪迅猛的行事。github上闹因redis版本的scrapy,这里我之想法是率先独爬虫负责爬页面的属一般爬虫(使用原版的scrapy),而第二单爬虫使用基于redis的爬虫。

  • 1 scrapy-redis安装
    pip install scrapy-redis
    安装方式倒是蛮粗略,但是是代码比较原始了,版本是0.6.3,这个本在python3.5臻行事无健康(出错跟转码有关str,具体情况不知晓),处理的方法就是是将0.6.7的代码下载下来直接盖就得了(反正我也看不懂代码,覆盖了能够工作)。
  • 2 配置
    scrapy-redis的部署或者于settings中,参照文档
    文档中有几单必须配备的参数:
    SCHEDULER = "scrapy_redis.scheduler.Scheduler"
    DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
    末端还好配备redis服务器端口号,还有redis服务器地址。
    REDIS_START_URLS_BATCH_SIZE
    地方的参数对代表每次redis从redis服务器被赢得的链接数量,这个调整强可能会见大增性能。
  • 3 页面爬虫

class BtSpiderEx(scrapy.Spider):
    name = 'btspiderex'
    start_urls = ['http://taohuabbs.info/forum-181-1.html']
    def parse(self,response):
        urls = response.xpath('//a[@onclick="atarget(this)"]/@href').extract()
        for url in urls:
            yield scrapy.Request(response.urljoin(url),callback=self.parsedetail)

        page_urls = response.xpath('//div[@class="pg"]/a/@href').extract()
        for url in page_urls:
            yield scrapy.Request(response.urljoin(url),callback=self.parse)

    def parsedetail(self,response):
        hrefs = response.xpath('//p[@class="attnm"]/a/@href').extract()
        for h in hrefs:
            yield scrapy.Request(response.urljoin(h),callback=self.parsedown)

    def parsedown(self,response):
        '''
        其实每次只能分析出一个bt链接
        '''
        bt_urls = response.xpath('//div[@style="padding-left:10px;"]/a/@href').extract()
        yield {'bt_urls':bt_urls}

页面爬虫代码其实相对于前的贯彻,变得愈加简约,这里拿将下充斥链接推送到redis服务器的任务交给pipeline。

class DownloadBittorrent(object):
    def __init__(self, dir_path):
        self.dir_path = dir_path
    @classmethod
    def from_crawler(cls, crawler):
        return cls(
            dir_path =crawler.settings.get('DIR_PATH'),
        )
    def open_spider(self, spider):
        if not os.path.exists(self.dir_path):
            os.makedirs(self.dir_path)
        self.conn = redis.Redis(port=6666)
    def close_spider(self,spdier):
        pass
    def process_item(self, item, spider):

        bt_urls = item['bt_urls']
        for url in bt_urls:
            self.conn.lpush('redisspider:start_urls',url)
        return item

open_spider于爬虫启动的上启动,这里就得打开redis和确立下载文件夹。redisspider:start_urls其一是redis队列名,缺省景象下scrapy-redis爬虫的队列就是爬虫名+start_urls。

  • 4 下充斥爬虫
    下载爬虫只承担从redis获取链接然后下充斥。

from scrapy_redis.spiders import RedisSpider
import re
class DistributeSpider(RedisSpider):
    name = 'redisspider'
    def parse(self,response):
        DIR_PATH = "D:/bt"
        if 'Content-Disposition' in response.headers:
            attachment = response.headers['Content-Disposition']
            pattern = re.compile(r'filename="(.+)"')
            filename = pattern.findall(attachment.decode('utf-8'))[0]
            filepath = '%s/%s' % (DIR_PATH,filename)#DIR_PATH = "D:/bt"
            with open(filepath, 'wb') as handle:
                handle.write(response.body)

settings.py配置,只排有了主要参数,这里修改了默认端口号:

SCHEDULER = "scrapy_redis.scheduler.Scheduler"
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
REDIS_PORT = 6666

同等   机器配置

1.1  机器组成

1台nameserver

1尊broker  异步刷盘

2台producer

2台consumer

 

1.2  硬件配备

CPU  两颗x86_64cpu,每颗cpu12核,共24核

内存 48G

网卡 千兆网卡

磁盘 除broker机器的磁盘是RAID10,共1.1T,其他都是屡见不鲜磁盘约500G

 

1.3  部署组织

橙色箭头为数量流向,黑色连接线为网络连接

澳门美高梅手机网站 1

 

 

1.4  内核参数

broker是一个存储型的系,针对磁盘读写起和好之刷盘策略,大量利用文件内存映射,文件句柄和内存消耗量都比较巨大。因此,系统的默认设置并无可知使RocketMQ发挥好好之性质,需要针对系统的pagecache,内存分配,I/O调度,文件句柄限制做一些对的参数设置。

 

 

系统I/O和虚拟内存设置

 

echo ‘vm.overcommit_memory=1’ >> /etc/sysctl.conf

 

echo ‘vm.min_free_kbytes=5000000’ >> /etc/sysctl.conf

 

echo ‘vm.drop_caches=1’ >> /etc/sysctl.conf

 

echo ‘vm.zone_reclaim_mode=0’ >> /etc/sysctl.conf

 

echo ‘vm.max_map_count=655360’ >> /etc/sysctl.conf

 

echo ‘vm.dirty_background_ratio=50’ >> /etc/sysctl.conf

 

echo ‘vm.dirty_ratio=50’ >> /etc/sysctl.conf

 

echo ‘vm.page-cluster=3’ >> /etc/sysctl.conf

 

echo ‘vm.dirty_writeback_centisecs=360000’ >> /etc/sysctl.conf

 

echo ‘vm.swappiness=10’ >> /etc/sysctl.conf

 

 

 

系统文件句柄设置

 

echo ‘ulimit -n 1000000’ >> /etc/profile

 

echo ‘admin hard nofile 1000000’ >> /etc/security/limits.conf

 

 

 

网I/O调度算法

 

deadline

 

 

 

1.5 JVM参数

 

运用RocketMQ默认设置

 

 -server -Xms4g -Xmx4g -Xmn2g -XX:PermSize=128m -XX:MaxPermSize=320m -XX:+UseConcMarkSweepGC -XX:+UseCMSCompactAtFullCollection -XX:CMSInitiatingOccupancyFraction=70 -XX:+CMSParallelRemarkEnabled -XX:SoftRefLRUPolicyMSPerMB=0 -XX:+CMSClassUnloadingEnabled -XX:SurvivorRatio=8 -XX:+DisableExplicitGC -verbose:gc -Xloggc:/root/rocketmq_gc.log -XX:+PrintGCDetails -XX:-OmitStackTraceInFastThrow 

 

 

 

第二   性能测评

2.1  评测目的

压测单机TPS,评估单机容量

 

2.2  评测指标

摩天的TPS不表示最契合的TPS,必须以TPS和系统资源各项指标中赢得一个权衡,系统资源快上极端,但还能健康运作,此时底TPS是较方便的。比如ioutil最好永不超过75%,cpu load最好不超过总的核数或者太多,没有来频繁之swap导致比较充分的内存颠簸。所以未可知单纯关注TPS,同时如果关心以下指标:

消息:TPS

cpu:load,sy,us

内存:useed,free,swap,cache,buffer

I/O:iops,ioutil,吞吐量(数据物理读写大小/秒)

网:网卡流量

 

2.3  评测方式

点滴华producer起等量线程,不中断的朝broker发送大小也2K之音讯,2K信表示1000单字符,这个信息终于比较特别了,完全好满足工作需。

 

2.4  评测结果

TPS比较高

   
经过长时测试与观赛,单个borker TPS高臻16000,也就是说服务器能够每秒处理16000漫漫信息,且消费端及时消费,从服务器存储消息及消费端消费完毕该信息平均时延约为1.3秒,且该时延不见面趁TPS变充分而转换充分,是只比较稳定的价。

 

Broker稳定性较高

    两玉producer一共启动44个线程10个钟头免停止发消息,broker非常平静,这可是略表示实际生育环境遭受可以出几十个producer向单台broker高频次发送信息,但是broker还会保持平静。在这样于特别之下压力下,broker的load最高才到3(24核的cpu),有大量的内存可用。

   
而且,连续10几小时测试中,broker的jvm非常稳定,没有产生同样不善fullgc,新生代GC回收效率非常强,内存没有其他压力,以下是选择自gclog的数额:

2014-07-17T22:43:07.407+0800: 79696.377: [GC2014-07-17T22:43:07.407+0800: 79696.377: [ParNew: 1696113K->18686K(1887488K), 0.1508800 secs] 2120430K->443004K(3984640K), 0.1513730 secs] [Times: user=1.36 sys=0.00, real=0.16 secs] 

新生代大小也2g,回收前内存占用约为1.7g,回收后内存占用17M左右,回收效率非常强。

 

 关于磁盘IO和内存

   
平均单个物理IO耗时约为0.06毫秒,IO几乎从来不额外等待,因为await和svctm基本相当。整个测试过程,没有起磁盘物理读,因为文件映射的关系,大量底cached内存将文件内容都缓存了,内存还有很酷之可用空间。

 

网的性质瓶颈

   
TPS到达16000后,再不怕高达无失矣,此时千兆网卡的各级秒流量大致为100M,基本达极端了,所以网卡是性瓶颈。不过,系统的IOUTIL最高都抵40%左右了,这个数字已不低了,所以即便网络流量增加,但是系统IO指标或者早已不正常了,总体来拘禁,单机16000之TPS是比较安全的值。

 

 

 

以下是各指标的趋势

TPS

TPS最高可以超越16000左右,再朝着上遏制,TPS有退趋势

 

澳门美高梅手机网站 2

 

 

 

CPU

 

随着线程数增加,最后稳定在3横,对于总共24审核的少数颗CPU,这点load根本不算什么

澳门美高梅手机网站 3

 

 

 

内存
内存非常平稳,总量48G,实际可用内存非常强

没有起swap交换,不见面为屡屡造访磁盘导致系统性能颠簸


大量内存为用来当文件缓存,见cached指标,极大的避免了磁盘物理读

 

澳门美高梅手机网站 4

 

 

 

磁盘吞吐量

 

乘机线程数增加,磁盘物理IO每秒数读写大约为70M左右

 

澳门美高梅手机网站 5

 

 

 

磁盘IOPS
乘势线程数增加,磁盘IOPS大约稳定于5000左右

在意充分重大的细节,即使以高及16000TPS时,磁盘仍然没发生物理读,这同内存的cached指标是相应的,文件几乎百分之百在内存里,没有生出同样不成物理读,所以文件读的频率很大,消息消费十分快

 

澳门美高梅手机网站 6

 

 

 

IO百分比

 

就线程数增加,IO百分比最后稳定于40%左右,这个数字可以接受

 

澳门美高梅手机网站 7

 

 

 

网络

 

网采取的千兆网卡,理论传输最特别价值吗128M/秒,实际能够达成100M尽管不错了。从图备受好观看,不断为上遏制请求,但是网卡流量就达成未失去了

 

澳门美高梅手机网站 8

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

发表评论

电子邮件地址不会被公开。 必填项已用*标注