本着交流和学习的心态和大家分享本人的第一篇博客(客套话就不说了,其实就是说说自己编写的思路和及对问题的解决办法)。

先说说技术路线,选择docker,scrapy,scrapy_redis 的原因很简单,省钱又方便。(苦比的大四党并不享用云主机优惠)

本爬虫主要抓取了豆瓣movie,book,music分类中的资源。

先看看最后抓取的数据量(大概12万的数据(爬虫待优化))

docker elk 分布式 docker分布式爬虫_docker elk 分布式

好了,下面就讲讲我的心路历程。

 

 

思路:

  url分析  --> 分析待抓取项 --> 编写 item  --> 构建mysql数据表  --> 解析待抓取项--> 编写spider -->

  编写pipeline --> 构建ip_pool --> 构建ua_pool --> 编写middleware -->调用scrapy_redis -->

   配置setting  --> 配置mysql,redis --> docker部署 -->crawl

  (大概就这样吧)

  

详解过程:

 1.url分析,分析待抓取项,编写item,构建mysql,解析待抓取项

    主要就规范了spider编写的思路以及通过re,xpath对内容进行提取规则(可使用scrapy shell 或者写个test),没啥可以详细写的。

  

 2.编写spider

 由于book,music,movie各自的数据提取规则和url规则并不一样,movie自身有json形式的数据,而其他俩个是html,所以需要在spider中对其进行分类编写。

 

if 'movie' in response.url:
            #豆瓣movie,由于豆瓣的movie自身是个api,只需要将这些url交给下一层提取url的函数处理
            movie_api_list =['https://movie.douban.com/j/new_search_subjects?sort=T&range=0,10&tags=%E7%94%B5%E5%BD%B1&start=0',
                            'https://movie.douban.com/j/new_search_subjects?sort=T&range=0,10&tags=%E7%94%B5%E8%A7%86%E5%89%A7&start=0',
                            'https://movie.douban.com/j/new_search_subjects?sort=T&range=0,10&tags=%E7%BB%BC%E8%89%BA&start=0',
                            'https://movie.douban.com/j/new_search_subjects?sort=T&range=0,10&tags=%E5%8A%A8%E7%94%BB&start=0',
                            'https://movie.douban.com/j/new_search_subjects?sort=T&range=0,10&tags=%E7%9F%AD%E7%89%87&start=0'
                             ]

            for movie_api in movie_api_list:
                yield Request(url=movie_api, callback=self.parse_detial)

        else:
            #处理书籍和音乐,将分类页面中所有可用的url都提取出来交给下一层处理
            all_urls = response.xpath('//a/@href').extract()
            if all_urls:
                all_urls = list(set([parse.urljoin(response.url,i) for i in all_urls]))
                for this_url in all_urls:
                    key_url = re.compile('(.*douban\.com/tag/.*)',re.DOTALL).findall(this_url)
                    if key_url:
                        key_url = key_url[0]
                        yield Request(url=key_url, callback=self.parse_detial)

 

   接下来为了提取二级分类,需要在url中提取数据,并将详细数据页面的url交给下个函数处理,下一页则递归(以movie为例子) 

#处理movie api
        if 'movie' in response.url:
            tag1='movie'
            movie_json = json.loads(response.text)
            movie_tag = re.compile('.*&tags=(.*?)&.*').findall(response.url)
            if movie_tag:
                #将url中的字符转换成可识别的字符串
                movie_tag = parse.unquote(movie_tag[0])
            else:
                movie_tag="None"
            for detial_url in movie_json["data"]:
                detial_url = detial_url["url"]
                #判断此api接口是否有数据
                if detial_url:
                    yield Request(url =detial_url,callback=self.get_data,meta={'tag1':tag1,'tag2':'{0}'.format(movie_tag)})
                    start = re.match(r'.*start=(\d+)',response.url)
                    if start:
                        num = str(int(start.group(1))+20)
                        page =re.compile('start=\d+')
                        next_movie_api =page.sub('start={0}'.format(num),response.url)
                        if next_movie_api:
                            yield Request(url=next_movie_api,callback=self.parse_detial)

  

    详细数据页面的提取使用了itemloader(以movie为例,简单的截个图吧) 

docker elk 分布式 docker分布式爬虫_spiders_02

    spider的编写就结束了。

 

  3.编写pipeline

  pipeline主要将数据写入到mysql中,担心mysql堵塞的问题,就借用了twisted异步插入mysql。

#通过twisted异步将数据插入mysql中,防止堵塞
class MysqlTwistedPipeline(object):
    #通过twisted框架调用adbapi实现异步的数据写入
    def __init__(self,dbpool):
        self.dbpool = dbpool

    @classmethod
    def from_settings(cls,settings):
        #通过settings中设置的数据库信息,将数据库导入到pool中
        dbparms =dict(
            host = settings["MYSQL_HOST"],
            db = settings["MYSQL_DBNAME"],
            user = settings["MYSQL_USER"],
            password = settings["MYSQL_PASSWORD"],
            charset = "utf8mb4",
            cursorclass = pymysql.cursors.DictCursor,
            use_unicode = True,
        )
        dbpool = adbapi.ConnectionPool("pymysql",**dbparms)
        return cls(dbpool)


    def process_item(self,item,spider):
        #调用twisted的api实现异步插入
        query = self.dbpool.runInteraction(self.do_insert,item)
        query.addErrback(self.handle_err,item,spider)
        print(">>>insert into mysql")


    def handle_err(self,failure,item,spider):
        print(failure)

    def do_insert(self,cursor,item):
        #具体的插入函数
        #根据不同的item,构建不同的sql语句,并且插入到mysql中
        #plan1 if item.__class__.__name__ =="ArticleItem" (通过item的名字来区别sql语句的写入)

        #plan 2 见items
        insert_sql,params =item.get_sql()
        # print(params[2])

  

  其中插入语句都写在item中(数据的精加工在Utill中):

def get_sql(self):
        insert_sql = '''
                      insert into movie(
                                    url,url_id,movie,classify,director,scriptwriter,
                                    actor,score,comments_num,screen_time,movie_type,country_areas,language,
                                    hot_comments,crawl_time
                                    )values(%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)
                      '''

        url = "".join(self.get("movie_url",""))
        url_id = "".join(self.get("movie_url_id",""))
        movie = get_ration_element(self.get("movie",""))
        classify = get_ration_element(self.get("movie_classify",""))
        director = get_ration_element(self.get("movie_director",""))
        scriptwriter = get_ration_element(self.get("movie_scriptwriter",""))
        actor = get_ration_element(self.get("movie_actor",""))
        type =  get_ration_element(self.get("movie_type",""))
        country_areas = get_ration_element(self.get("movie_country_areas",""))
        language = get_ration_element(self.get("movie_language",""))
        screen_time = get_date(self.get("movie_screen_time",""))
        score = get_num2(self.get("movie_score",""))
        comments_num = get_num1(self.get("movie_comments_num",""))
        hot_comments = get_hot_comments(self.get("movie_hot_comments",""))
        crawl_time = now()

        params = (url,url_id,movie,classify,director,scriptwriter,
                  actor,score,comments_num,screen_time,type,country_areas,language,
                  hot_comments,crawl_time)

        return insert_sql,params

 

  4.构建ip_pool

    使用了代理ip商提供的ip,将ip保存至本地文件中,定时更换,确保ip的效用。(墙裂推荐蘑菇代理,便宜又高效)。

  

  5.构建ua_pool

    fake-useragent包有250个ua,可以pip安装使用。见文档 https://github.com/hellysmile/fake-useragent

 

  6.编写Middleware

    只需要编写随机ua和代理ip,settings中关闭默认middleware,开启自己写的即可。

#随机生成ua并调用
class RandomUAMiddlerware(object):
    def __init__(self,crawler):
        super(RandomUAMiddlerware,self).__init__()
        self.ua = ua()

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

    def process_request(self,spider,request):
        request.headers.setdefault(b'User-Agent',self.ua)

#调用ippool池中的proxy并形成request
class RandomProxyIPMiddlware(object):
    def process_request(self, request, spider):
        proxy =product_ip()
        print("this_ip:"+str(proxy))
        request.meta["proxy"] = proxy

 

7.scrapy_redis

    scrapy_redis的使用很方便,直接附上其github  https://github.com/rmax/scrapy-redis

    记得跟换spider的超类为RedisSpider,并设置redis_key 。settings中需要的设置见文档即可,很详细实用

from scrapy_redis.spiders import RedisSpider

class BasicDoubanSpider(RedisSpider):
    name = 'basic_douban'
    redis_key = 'DouBan:start_urls'#设置redis的起始key
    # allowed_domains = ['www.douban.com']
#    start_urls = [  'https://music.douban.com/tag/',
 #                   'https://movie.douban.com/tag/#/',
  #                  'https://book.douban.com/tag/?view=type&icn=index-sorttags-all',
                    #]

 

  8.配置settings

    settings中需要关闭cookie,禁止REDIRECT,开启延迟,retry_code,retry次数,retry的优先级等等。不贴代码了,见我github吧 

  

  9.redis和mysql的配置

    redis:进入redis.conf文件将bind更改为主机doker的ip  (终端ifconfig查看)

     mysql:1 进入配置文件注释bind,使本地mysql可以访问

          2 添加用户并赋予权限 :mysql中 grant all privileges on *.* to "name"@"%" indetified by password;

 

10.docker 部署

    宿主机共享文件夹(共享ip-pool和spider)  创建contianer时  docker run -tiv 主机文件path:docker path image_id 

 

 11 可以开始crawl啦

    往scrapy_redis 中添加start-urls   lpush your redis_key+your start_url

 

好了,详细的过程就是这样子,具体代码见我的github吧。https://github.com/kilort/douban