一.环境搭建

1.创建环境

  • 执行pip install scrapy安装scrapy
  • 使用scrapy startproject ArticleSpider创建scrapy项目
  • 使用pycharm导入创建的scrapy项目

2.项目结构

  • scrapy.cfg:scrapy配置文件
  • settings.py:配置代码文件信息
  • pipelines.py:和数据存储相关的文件
  • middlewares.py:存放中间件
  • items.py:定义数据保存的格式
  • spiders文件夹存放爬虫文件

3.使用命令创建爬虫文件

  • scrapy genspider 爬虫名称 网站域名在spiders文件夹中创建爬虫文件

二.xpath编写爬虫代码

1.编写设计

  • pycharm没有scrapy模板,所以没法调试,我们可以通过创建main.py文件调用命令行,完成调试。

  • 在项目外侧创建main.py,代码如下

    #execute函数可以执行scrapy脚本
    from scrapy.cmdline import execute
    #需要sys获得工程目录
    import sys
    #利用os获得工程目录
    import os
    #需要设置工程目录,设置完工程目录之后调用execute函数才会生效
    #print(os.path.dirname(os.path.abspath(__file__)))则会获取到当前main文件的路径的上一级包名
    sys.path.append(os.path.dirname(os.path.abspath(__file__)))
    #execute相当于执行命令
    execute(["scrapy","crawl","jobbole"])
    
  • 【注意】:设置setting中的ROBOTSTXT_OBEY=False,如果不设置scrapy会默认读取每一个网站上的robots协议,会把不符合协议的url给过滤掉,很快就会执行结束

  • 执行debug main则会以debug模式运行爬虫

2.利用xpath提取值

  • xpath简介

    • xpath使用路径表达式在xml和html中进行导航
    • xpath包括标准函数库
    • xpath是一个w3c的标准
  • xpath节点关系

    • 父节点
    • 子节点
    • 同胞节点
    • 先辈节点
    • 后代节点
  • xpath语法

    表达式 说明
    article 选取所有article元素的所有子节点
    /article 选取根元素article
    article/a 选取所有属于article的子元素的a元素
    //div 选取所有div子元素(不论出现在文档的任何地方)
    article//div 选取所有属于article元素的后代的div元素,不管它出现在article之下的任何位置
    //@class 选取所有名为class的属性
  • xpath语法-谓语

    表达式 说明
    /article/div[1] 选取属于article子元素的第一个div元素
    /article/div[last()] 选取属于article子元素的最后一个div元素
    /article/div[last()-1] 选取属于article子元素的倒数第二个div元素
    //div[@lang] 选取所有拥有lang属性的div元素
    //div[@lang=‘eng’] 选取所有lang属性为eng的div元素
  • xpath其他语法

    表达式 说明
    /div/* 选取属于div元素的所有子节点
    //* 选取所有元素
    //div[@*] 选取所有带属性的div元素
    //div/a | //div/p 选取所有div元素的a和p元素
    //span | //ul 选取文档中的span和ul元素
    article/div/p | //span 选取所有属于article元素和div元素的p元素 以及文档中所有span元素
    • 如果想通过属性取值则需要给定标签元素的内容,如果是任意标签则给定*
    • 如果通过@class="class类"取值,则只会匹配class只有指定的元素;如果想指定包含指定class的元素则需要使用函数contains(@class,"class类")
  • xpath使用

    • [注意]:页面上的查看源码跟检查控制台的element不一定一样,源码是源代码的html文件,检查控制台的element会有js动态生成的dom

    • jobbole.py

      # -*- coding: utf-8 -*-
      import scrapyclass JobboleSpider(scrapy.Spider):name = 'jobbole'allowed_domains = ['blog.jobbole.com']start_urls = ['http://blog.jobbole.com/114610/'] #放入想爬取的所有urldef parse(self, response):#/html/body/div[3]/div[3]/div[1]/div[1]#//*[@id="post-114610"]/div[1]/h1#scrapy返回的是一个selector而不是node,是为了方便进一步获取selector下面的selectorre_selector = response.xpath('//*[@id="post-114610"]/div[1]/h1')re2_selector = response.xpath('//*[@id="post-114610"]/div[1]/h1/text()') #利用text()函数获取元素中的值pass
      
  • 如果每一次调试都运行python脚本发送http请求获取内容效率低,可以通过scrapy shell url路径的方式通过shell进行调试

    #以下是在命令行输入
    scrapy shell http://blog.jobbole.com/114610/
    re_selector = response.xpath('//*[@id="post-114610"]/div[1]/h1')
    re_selector.extract()#提取selector的内容['<h1>微软变了!招程序员的流程完全改了</h1>']
    re_selector.extract()[0]#获得列表中的第一个元素 '<h1>微软变了!招程序员的流程完全改了</h1>'
    #如果提取的字符串左右有回车符换行符等等,则需要使用strip()将其去掉
    #re_selector.extract()[0].strip()
    
  • 使用xpath爬取伯乐在线网站内容[jobbole.py]

    # -*- coding: utf-8 -*-
    import scrapy
    import reclass JobboleSpider(scrapy.Spider):name = 'jobbole'allowed_domains = ['blog.jobbole.com']start_urls = ['http://blog.jobbole.com/114610/'] #放入想爬取的所有urldef parse(self, response):# 获取文章titletitle = response.xpath('//*[@id="post-114610"]/div[1]/h1/text()')# 获取文章创建时间create_date = response.xpath('//*[@id="post-114610"]/div[2]/p/text()').extract()[0].strip().replace("·","").strip()# 通过contains函数选择class中包含vote-post-up的元素,获得点赞数praise = response.xpath('//*[contains(@class,"vote-post-up")]/h10/text()').extract()[0]# 收藏数,获取的是字符串,需要使用正则表达式取出collect = response.xpath('//*[contains(@class,"bookmark-btn")]/text()').extract()[0]match_re = re.match(".*?(\d+).*",collect)if match_re:collect = match_re.group(1)else:collect = 0# 评论数comment = response.xpath('//a[@href="#article-comment"]/span/text()').extract()[0]match_re = re.match(".*?(\d+).*", comment)if match_re:comment = match_re.group(1)else:comment = 0# 文章内容content = response.xpath('//div[@class="entry"]').extract()[0]# 文章标签tag = response.xpath('//*[@id="post-114610"]/div[2]/p/a/text()').extract()# 利用列表生成式过滤携带评论的文章标签tag = [element for element in tag if not element.strip().endswith("评论")]# 利用join方式将列表拼成一个字符串tags = ",".join(tag)pass
    

三.css选择器编写爬虫代码

1.css选择器语法

表达式 说明
* 选择所有节点
#container 选择id为container的节点
.container 选取所有class包含container的节点
li a 选取所有li下的所有a节点
ul + p 选择ul后面的第一个p元素
div#container>ul 选取id为container的第一个ul子元素
ul ~ p 选取与ul相邻的所有p元素
a[title] 选取所有有title属性的a元素
a[href=“http://jobbole.com”] 选取所有href属性为jobbole.com值的a元素
a[href*=“jobble”] 选取所有href属性包含jobbole的a元素
a[href^=“http”] 选取所有href属性以http开头的a元素
a[href$=".jpg"] 选取所有href属性以jpg结尾的a元素
input[type=radio]:checked 选择选中的radio元素
div:not(#container) 选取所有id非container的div属性
li:nth-child(3) 选取第三个li元素
tr:nth-child(2n) 第偶数个tr
::text 利用伪类选择器获得选中的元素的内容

2.利用css选择器提取值

  • 使用response.css("选择器内容")通过css选择器获得内容

  • 利用css选择器提取伯乐在线平台内容[jobbole.py]

    # -*- coding: utf-8 -*-
    import scrapy
    import reclass JobboleSpider(scrapy.Spider):name = 'jobbole'allowed_domains = ['blog.jobbole.com']start_urls = ['http://blog.jobbole.com/114610/'] #放入想爬取的所有urldef parse(self, response):######################## 利用css选择器获取内容######################title = response.css(".entry-header h1::text").extract()[0]create_date = response.css("p.entry-meta-hide-on-mobile::text").extract()[0].strip().replace("·", "").strip()# 点赞数praise_nums = response.css(".vote-post-up h10::text").extract()[0]# 收藏数collect = response.css('span.bookmark-btn::text').extract()[0]match_re = re.match(".*?(\d+).*", collect)if match_re:collect = match_re.group(1)# 评论数comment = response.css('a[href="#article-comment"] span::text').extract()[0]match_re = re.match(".*?(\d+).*", comment)if match_re:comment = match_re.group(1)# 获得文章内容content = response.css("div.entry").extract()[0]# 获得标签tags = response.css("p.entry-meta-hide-on-mobile a::text").extract()tag = [element for element in tags if not element.strip().endswith("评论")]tags = ",".join(tag)pass
    
  • 如果想使用extract将selector变成list后取得第一个除了使用extract()[0]之后还可以使用extract_first(),如果获取的是空的话,使用extract()[0]会报错,而extract_first()不会报错,还可以在参数设定默认值,如果没有值则返回对应的内容

四.真实爬取数据

1.获取文章列表页中的文章url并交给scrapy下载后并进行解析

2.获取下一页url并交给scrapy进行下载,下载完成后交给parse

  • 实例代码[jobbole.py]

    # -*- coding: utf-8 -*-
    import scrapy
    import re
    from scrapy.http import Request
    from urllib import parseclass JobboleSpider(scrapy.Spider):name = 'jobbole'allowed_domains = ['blog.jobbole.com']start_urls = ['http://blog.jobbole.com/all-posts/'] #放入想爬取的所有urldef parse(self, response):"""1.获取文章列表页中的文章url并交给scrapy下载后并进行解析2.获取下一页url并交给scrapy进行下载,下载完成后交给parse:param response::return:"""#解析列表页的所有文章url并交给scrapy下载后进行解析#利用::attr()伪类选择器获得对应元素的属性值post_urls = response.css("#archive .floated-thumb .post-thumb a::attr(href)").extract()for post_url in post_urls:#利用Request方法提交请求获取对应url内容#url表示访问的url(parse.urljoin是借助urllib将当前url与请求url进行拼接,从而获取到真实的url),callback表示回调的函数yield Request(url=parse.urljoin(response.url,post_url),callback=self.parse_detail)#提交下一页并交给scrapy进行下载#两个类之间有空格则表示子元素,两个类中间没空格则表示同时满足两个类的元素next_url = response.css(".next.page-numbers::attr(href)").extract_first("")if next_url:yield Request(url=parse.urljoin(response.url, next_url), callback=self.parse)#提取文章的具体字段def parse_detail(self,response):######################## 利用css选择器获取内容######################title = response.css(".entry-header h1::text").extract()[0]print(title)create_date = response.css("p.entry-meta-hide-on-mobile::text").extract()[0].strip().replace("·", "").strip()# 点赞数praise_nums = response.css(".vote-post-up h10::text").extract()[0]# 收藏数collect = response.css('span.bookmark-btn::text').extract()[0]match_re = re.match(".*?(\d+).*", collect)if match_re:collect = match_re.group(1)else:collect = 0# 评论数comment = response.css('a[href="#article-comment"] span::text').extract()[0]match_re = re.match(".*?(\d+).*", comment)if match_re:comment = match_re.group(1)else:comment = 0# 获得文章内容content = response.css("div.entry").extract()[0]# 获得标签tags = response.css("p.entry-meta-hide-on-mobile a::text").extract()tag = [element for element in tags if not element.strip().endswith("评论")]tags = ",".join(tag)
    
  • 开发流程

    • 利用Request函数执行访问指定url并通过callback回调函数处理进入url后的操作
    • 利用parse.urljoin自动将对应url添加域名,参数1是域名,参数2是url
    • 利用yield实现异步请求
    • 利用::attr()伪类选择器获取对应属性的值

五.数据存储

数据爬取的目的在于将非结构的数据源提取成结构型的数据

如果想使用Request函数向callback函数中传递内容,则需要使用meta参数

1.使用meta向Request函数的callback中传递内容

  • 需求说明:在列表页获取图片url连接传递到parse_detail函数进行解析

  • 实现思路

    • 获取时需要将之前直接获取a下的href变成先获取node节点,之后获得a下的img的src,再获取a的href,在使用Request函数中添加meta参数内容放上img_url传递给callback函数中的response
    • 在response中获取之前存入的img_url,使用response.meta.get("front_image_url","")
  • 实例代码

    # -*- coding: utf-8 -*-
    import scrapy
    import re
    from scrapy.http import Request
    from urllib import parseclass JobboleSpider(scrapy.Spider):name = 'jobbole'allowed_domains = ['blog.jobbole.com']start_urls = ['http://blog.jobbole.com/all-posts/'] #放入想爬取的所有urldef parse(self, response):"""1.获取文章列表页中的文章url并交给scrapy下载后并进行解析2.获取下一页url并交给scrapy进行下载,下载完成后交给parse:param response::return:"""#解析列表页的所有文章url并交给scrapy下载后进行解析#利用::attr()伪类选择器获得对应元素的属性值post_nodes = response.css("#archive .floated-thumb .post-thumb a")for post_node in post_nodes:image_url = post_node.css("img::attr(src)").extract_first("") #获得图片的srcpost_url = post_node.css("::attr(href)").extract_first("") #获得连接的href#利用Request方法提交请求获取对应url内容#url表示访问的url(parse.urljoin是借助urllib将当前url与请求url进行拼接,从而获取到真实的url),callback表示回调的函数yield Request(url=parse.urljoin(response.url,post_url),meta={"front_image_url":image_url},callback=self.parse_detail)#提交下一页并交给scrapy进行下载#两个类之间有空格则表示子元素,两个类中间没空格则表示同时满足两个类的元素next_url = response.css(".next.page-numbers::attr(href)").extract_first("")if next_url:yield Request(url=parse.urljoin(response.url, next_url), callback=self.parse)#提取文章的具体字段def parse_detail(self,response):######################## 利用css选择器获取内容####################### 获得meta中的front_image_url,文章封面图front_image_url = response.meta.get("front_image_url","")# 获得title title = response.css(".entry-header h1::text").extract()[0]print(title)create_date = response.css("p.entry-meta-hide-on-mobile::text").extract()[0].strip().replace("·", "").strip()# 点赞数praise_nums = response.css(".vote-post-up h10::text").extract()[0]# 收藏数collect = response.css('span.bookmark-btn::text').extract()[0]match_re = re.match(".*?(\d+).*", collect)if match_re:collect = match_re.group(1)else:collect = 0# 评论数comment = response.css('a[href="#article-comment"] span::text').extract()[0]match_re = re.match(".*?(\d+).*", comment)if match_re:comment = match_re.group(1)else:comment = 0# 获得文章内容content = response.css("div.entry").extract()[0]# 获得标签tags = response.css("p.entry-meta-hide-on-mobile a::text").extract()tag = [element for element in tags if not element.strip().endswith("评论")]tags = ",".join(tag)
    

2.item

相当于定义model

  • 在items.py文件中定义item

    class JobBoleArticleItem(scrapy.Item):title = scrapy.Field() #scrapy可以使用Field表示任意类型create_date = scrapy.Field()url = scrapy.Field()url_object_id = scrapy.Field() #对url进行编码front_image_url = scrapy.Field()front_image_path = scrapy.Field()praise_nums = scrapy.Field()comment_nums = scrapy.Field()fav_nums = scrapy.Field()tags = scrapy.Field()content = scrapy.Field()
    
  • 在jobbole.py文件中定义爬虫返回内容放到item中

    # -*- coding: utf-8 -*-
    import scrapy
    import re
    from scrapy.http import Request
    from urllib import parse#引入item
    from ArticleSpider.items import JobBoleArticleItemclass JobboleSpider(scrapy.Spider):name = 'jobbole'allowed_domains = ['blog.jobbole.com']start_urls = ['http://blog.jobbole.com/all-posts/'] #放入想爬取的所有urldef parse(self, response):"""1.获取文章列表页中的文章url并交给scrapy下载后并进行解析2.获取下一页url并交给scrapy进行下载,下载完成后交给parse:param response::return:"""#解析列表页的所有文章url并交给scrapy下载后进行解析#利用::attr()伪类选择器获得对应元素的属性值post_nodes = response.css("#archive .floated-thumb .post-thumb a")for post_node in post_nodes:image_url = post_node.css("img::attr(src)").extract_first("") #获得图片的srcpost_url = post_node.css("::attr(href)").extract_first("") #获得连接的href#利用Request方法提交请求获取对应url内容#url表示访问的url(parse.urljoin是借助urllib将当前url与请求url进行拼接,从而获取到真实的url),callback表示回调的函数yield Request(url=parse.urljoin(response.url,post_url),meta={"front_image_url":image_url},callback=self.parse_detail)#提交下一页并交给scrapy进行下载#两个类之间有空格则表示子元素,两个类中间没空格则表示同时满足两个类的元素next_url = response.css(".next.page-numbers::attr(href)").extract_first("")if next_url:yield Request(url=parse.urljoin(response.url, next_url), callback=self.parse)#提取文章的具体字段def parse_detail(self,response):######################## 利用css选择器获取内容####################### 实例化itemarticle_item = JobBoleArticleItem()# 获得meta中的front_image_url,文章封面图front_image_url = response.meta.get("front_image_url","")# 获得titletitle = response.css(".entry-header h1::text").extract()[0]print(title)create_date = response.css("p.entry-meta-hide-on-mobile::text").extract()[0].strip().replace("·", "").strip()# 点赞数praise_nums = response.css(".vote-post-up h10::text").extract()[0]# 收藏数collect = response.css('span.bookmark-btn::text').extract()[0]match_re = re.match(".*?(\d+).*", collect)if match_re:collect = match_re.group(1)else:collect = 0# 评论数comment = response.css('a[href="#article-comment"] span::text').extract()[0]match_re = re.match(".*?(\d+).*", comment)if match_re:comment = match_re.group(1)else:comment = 0# 获得文章内容content = response.css("div.entry").extract()[0]# 获得标签tags = response.css("p.entry-meta-hide-on-mobile a::text").extract()tag = [element for element in tags if not element.strip().endswith("评论")]tags = ",".join(tag)#填充itemarticle_item['title'] = titlearticle_item['url'] = response.urlarticle_item['create_date'] = create_datearticle_item['front_image_url'] = [front_image_url]article_item['praise_nums'] = praise_numsarticle_item['comment_nums'] = commentarticle_item['fav_nums'] = collectarticle_item['tags'] = tagsarticle_item['content'] = contentyield article_item #将item传递到pipeline中
    

3.pipeline

  • pipeline主要用于做数据处理

  • item赋值之后就会传递到pipeline.py中,但需要将settings中的

    ITEM_PIPELINES = {'ArticleSpider.pipelines.ArticlespiderPipeline': 300,
    }
    

    取消注释

  • 在settings中设置下载图片的pipeline(scrapy已经给提供了,在site-package->scrapy->pipelines->images.py->ImagesPipeline),添加到配置的ITEM_PIPELINES中(为item流经的管道,后面的数字表示处理顺序,数字越小就越早进入pipeline)

    #先进入ImagePipeline再进入ArticleSpiderPipeline
    ITEM_PIPELINES = {'ArticleSpider.pipelines.ArticlespiderPipeline': 300,'scrapy.pipelines.images.ImagesPipeline': 1,
    }
    
  • ImagePipeline主要用于存储图片,需要在setting中配置下载传递item中的哪个字段,想保存图片还需要安装PIL库pip install pillow

    import os
    #先进入ImagePipeline再进入ArticleSpiderPipeline
    ITEM_PIPELINES = {'ArticleSpider.pipelines.ArticlespiderPipeline': 300,'scrapy.pipelines.images.ImagesPipeline': 1,
    }
    #配置下载的是哪些字段,将指定的字段视作一个数组,故item中图片url应为数组格式
    IMAGES_URLS_FIELD = "front_image_url"
    #设置图片保存路径[先创建一个images文件夹]
    project_dir = os.path.abspath(os.path.dirname(__file__)) #获得当前文件夹的绝对路径
    IMAGES_STORE = os.path.join(project_dir,"images")
    
  • 此时执行main.py会发现在images文件夹下会存储对应的网络图片

  • 自定义存储图片pipeline

    • 配置setting.py,设置过滤掉的图片(表示必须大于100*100)

      ITEM_PIPELINES = {'ArticleSpider.pipelines.ArticlespiderPipeline': 300,'scrapy.pipelines.images.ImagesPipeline': 1,
      }IMAGES_URLS_FIELD = "front_image_url"
      project_dir = os.path.abspath(os.path.dirname(__file__))
      IMAGES_STORE = os.path.join(project_dir,"images")
      # 表示下载的图片必须要是大于100*100的
      #IMAGES_MIN_HEIGHT = 100 #在ImagesPipeline中使用到了此变量
      #IMAGES_MIN_WIDTH = 100 #在ImagesPipeline中使用到了此变量
      
    • ImagesPipeline中的item_completed函数中可以获取到图片的实际下载地址(需要重载)

    • pipelines.py实例代码

      # -*- coding: utf-8 -*-# Define your item pipelines here
      #
      # Don't forget to add your pipeline to the ITEM_PIPELINES setting
      # See: https://doc.scrapy.org/en/latest/topics/item-pipeline.html
      from scrapy.pipelines.images import ImagesPipelineclass ArticlespiderPipeline(object):def process_item(self, item, spider):return item# 继承ImagePipeline
      class ArticleImagePipeline(ImagesPipeline):def item_completed(self, results, item, info):# result中包含一个tuple,一个是状态值,一个是返回对象for ok,value in results:image_file_path = value["path"]item["front_image_path"] = image_file_pathreturn item
      
  • 自定义存储json的pipeline

    • setting.py配置调用pipeline

      ITEM_PIPELINES = {'ArticleSpider.pipelines.ArticlespiderPipeline': 300,'ArticleSpider.pipelines.JsonWithEncodingPipeline': 2, #添加存储到json文件的pipeline'ArticleSpider.pipelines.ArticleImagePipeline': 1,# 'scrapy.pipelines.images.ImagesPipeline': 1,
      }
      
    • pipelines.py配置存储json的pipeline

      class JsonWithEncodingPipeline(object):def __init__(self):# 利用codecs方式打开文件,与open函数不同在于编码,可以省去很多编码方面的繁杂工作self.file =  codecs.open("article.json","w",encoding="utf-8")def process_item(self, item, spider):#利用json.dumps函数将item存储成jsonlines = json.dumps(dict(item),ensure_ascii=False) + "\n"#写入json文件中self.file.write(lines)#process_item函数一定要返回item,因为下一个pipeline还会使用此itemreturn item#在spider close的时候关闭filedef spider_closed(self,spider):self.file.close()
      
      • 首先利用codecs.open打开文件
      • 使用write方法将转换的json写入
      • 使用file的close方法关闭文件写入
    • scrapy提供了方便的exporters机制,帮助我们很方便的将item导出成各种类型的文件[前提是setting.py中调用此pipeline,此处省略去写]

      # 利用json exporter
      class JsonExporterPipeline(object):#调用scrapy提供的json exporter导出json文件def __init__(self):self.file = open('articleexporter.json','wb') # 二进制方式打开self.exporter = JsonItemExporter(self.file,encoding = "utf-8",ensure_ascii=False)self.exporter.start_exporting()def process_item(self, item, spider):self.exporter.export_item(item)return itemdef close_spider(self,spider):self.exporter.finish_exporting()self.file.close()
      
      • 使用open方法打开文件并创建exporter
      • 执行exporter的start_exporting方法开启exporter
      • 执行exporter的export_item方法将item写入exporter
      • 执行exporter的finish_exporting方法结束export
      • 关闭文件

4.使用MySQL存储数据

  • 修改jobbole.py中的create_date为date类型(便于存储到mysql中的date类型)

  • 创建sql表

  • 安装mysql驱动pip install mysqlclient

  • 定义数据存储pipeline,直接将item存储到mysql中

    class MysqlPipeline(object):def __init__(self):#连接mysqldb,创建连接self.conn = MySQLdb.connect('127.0.0.1','root','123456','scrapy',charset="utf8",use_unicode=True)#创建cursorself.cursor = self.conn.cursor()def process_item(self, item, spider):insert_sql = """insert into jobbole_article(title,create_date,url,url_object_id,front_image_url,front_image_path,comment_nums,fav_nums,praise_nums,tags,content)VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)"""self.cursor.execute(insert_sql,(item["title"],item["create_date"],item["url"],item["url_object_id"],item["front_image_url"],item["front_image_path"],item["comment_nums"],item["fav_nums"],item["praise_nums"],item["tags"],item["content"]))self.conn.commit()
    
  • 上述方法(execute和commit操作是同步操作)在后期爬取加解析会快于数据存储到mysql,会导致阻塞。使用twisted框架提供的api可以完成数据的异步写入。

    • 在setting.py中配置相关数据信息

      MYSQL_HOST = "127.0.0.1"
      MYSQL_DBNAME = "scrapy"
      MYSQL_USER = "root"
      MYSQL_PASSWORD = "123456"
      
    • 使用twisted框架实现异步的数据写入

      #异步mysql插入
      class MysqlTwistedPipeline(object):#setting中包含setting.py中的内容@classmethoddef from_settings(cls,settings):dbparms = dict(host = settings["MYSQL_HOST"],db = settings["MYSQL_DBNAME"],user = settings["MYSQL_USER"],passwd = settings["MYSQL_PASSWORD"],charset = "utf8",cursorclass = MySQLdb.cursors.DictQursor,use_unicode = True)#创建连接池,参数1使用的dbapi的name,dbpool = adbapi.ConnectionPool("MySQLdb",**dbparms)return cls(dbpool)def __init__(self,dbpool):self.dbpool = dbpooldef process_item(self, item, spider):#使用twisted将mysql插入变成异步执行,参数1表示要异步执行的函数,参数2表示执行的itemquery= self.dbpool.runInteraction(self.do_insert,item)query.addErrback(self.handle_error)#处理异常def do_insert(self,cursor,item):#执行具体的插入insert_sql = """insert into jobbole_article(title,create_date,url,url_object_id,front_image_url,front_image_path,comment_nums,fav_nums,praise_nums,tags,content)VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)"""cursor.execute(insert_sql, (item["title"], item["create_date"], item["url"], item["url_object_id"], item["front_image_url"],item["front_image_path"], item["comment_nums"], item["fav_nums"], item["praise_nums"], item["tags"],item["content"]))#不需要commit,会自动进行commitdef handle_error(self,failure):#处理异步插入的异常print(failure)
      

5.使用ItemLoader对item进行统一管理初试

  • 设计思路

    • 使用itemLoader统一使用add_css/add_xpath/add_value方法获取对应数据并存储到item中
    • 在item中使用scrapy.Field的参数input_processor执行MapCompose方法执行对输入值的多次函数处理
  • jobbole.py

    # -*- coding: utf-8 -*-
    import scrapy
    import re
    from scrapy.http import Request
    from urllib import parse
    import datetime
    from scrapy.loader import ItemLoader#引入item
    from ArticleSpider.items import JobBoleArticleItem
    #引入md5处理url
    from ArticleSpider.utils.common import get_md5class JobboleSpider(scrapy.Spider):name = 'jobbole'allowed_domains = ['blog.jobbole.com']start_urls = ['http://blog.jobbole.com/all-posts/'] #放入想爬取的所有urldef parse(self, response):"""1.获取文章列表页中的文章url并交给scrapy下载后并进行解析2.获取下一页url并交给scrapy进行下载,下载完成后交给parse:param response::return:"""#解析列表页的所有文章url并交给scrapy下载后进行解析#利用::attr()伪类选择器获得对应元素的属性值post_nodes = response.css("#archive .floated-thumb .post-thumb a")for post_node in post_nodes:image_url = post_node.css("img::attr(src)").extract_first("") #获得图片的srcpost_url = post_node.css("::attr(href)").extract_first("") #获得连接的href#利用Request方法提交请求获取对应url内容#url表示访问的url(parse.urljoin是借助urllib将当前url与请求url进行拼接,从而获取到真实的url),callback表示回调的函数yield Request(url=parse.urljoin(response.url,post_url),meta={"front_image_url":image_url},callback=self.parse_detail)#提交下一页并交给scrapy进行下载#两个类之间有空格则表示子元素,两个类中间没空格则表示同时满足两个类的元素next_url = response.css(".next.page-numbers::attr(href)").extract_first("")if next_url:yield Request(url=parse.urljoin(response.url, next_url), callback=self.parse)#提取文章的具体字段def parse_detail(self,response):######################## 利用css选择器获取内容####################### 实例化itemarticle_item = JobBoleArticleItem()# 获得meta中的front_image_url,文章封面图front_image_url = response.meta.get("front_image_url","")# 获得titletitle = response.css(".entry-header h1::text").extract()[0]print(title)create_date = response.css("p.entry-meta-hide-on-mobile::text").extract()[0].strip().replace("·", "").strip()# 点赞数praise_nums = response.css(".vote-post-up h10::text").extract()[0]# 收藏数collect = response.css('span.bookmark-btn::text').extract()[0]match_re = re.match(".*?(\d+).*", collect)if match_re:collect = match_re.group(1)else:collect = 0# 评论数comment = response.css('a[href="#article-comment"] span::text').extract()[0]match_re = re.match(".*?(\d+).*", comment)if match_re:comment = match_re.group(1)else:comment = 0# 获得文章内容content = response.css("div.entry").extract()[0]# 获得标签tags = response.css("p.entry-meta-hide-on-mobile a::text").extract()tag = [element for element in tags if not element.strip().endswith("评论")]tags = ",".join(tag)#填充itemarticle_item['title'] = title#判断是否有日期,有的话则格式化成对应格式,没有的话则是当前日期try:create_date = datetime.datetime.strptime(create_date,"%Y%m%d").date()except Exception as e:create_date = datetime.datetime.now().date()article_item['create_date'] = create_datearticle_item['url'] = response.urlarticle_item['url_object_id'] = get_md5(response.url)article_item['front_image_url'] = [front_image_url]article_item['praise_nums'] = praise_numsarticle_item['comment_nums'] = commentarticle_item['fav_nums'] = collectarticle_item['tags'] = tagsarticle_item['content'] = content#Item loader加载itemitem_loader = ItemLoader(item=JobBoleArticleItem(),response=response)#使用css方式向item loader中填充值item_loader.add_css("title",".entry-header h1::text")item_loader.add_css("create_date","p.entry-meta-hide-on-mobile::text")item_loader.add_css("praise_nums",".vote-post-up h10::text")item_loader.add_css("comment_nums","a[href='#article-comment'] span::text")item_loader.add_css("fav_nums","span.bookmark-btn::text")item_loader.add_css("tags","p.entry-meta-hide-on-mobile a::text")item_loader.add_css("content","div.entry")#使用value方式向item_loader中填充值item_loader.add_value("url",response.url)item_loader.add_value("url_object_id",get_md5(response.url))item_loader.add_value("front_image_url",[front_image_url])article_item = item_loader.load_item()yield article_item #将item传递到pipeline中
    
    • 使用ItemLoader加载item,并使用add_css/add_xpath/add_value方法添加数值
  • items.py

    # -*- coding: utf-8 -*-# Define here the models for your scraped items
    #
    # See documentation in:
    # https://doc.scrapy.org/en/latest/topics/items.htmlimport scrapy
    from scrapy.loader.processors import MapComposeclass ArticlespiderItem(scrapy.Item):# define the fields for your item here like:# name = scrapy.Field()pass#处理的函数,value表示input的值,即初始值
    def add_jobbole(value):return value+"-jobbole"class JobBoleArticleItem(scrapy.Item):title = scrapy.Field(input_processor = MapCompose(add_jobbole) #Mapcompose表示可以对传入的内容调用多个函数进行预处理) #scrapy可以使用Field表示任意类型create_date = scrapy.Field()url = scrapy.Field()url_object_id = scrapy.Field() #对url进行编码front_image_url = scrapy.Field()front_image_path = scrapy.Field()praise_nums = scrapy.Field()comment_nums = scrapy.Field()fav_nums = scrapy.Field()tags = scrapy.Field()content = scrapy.Field()
    
    • 使用MapCompose方法多次调用执行函数

6.使用ItemLoader对item进行统一调整并通过配置item实现数据处理

  • items.py添加对字段的处理内容

    # -*- coding: utf-8 -*-# Define here the models for your scraped items
    #
    # See documentation in:
    # https://doc.scrapy.org/en/latest/topics/items.htmlimport datetime
    import scrapy
    import re
    from scrapy.loader.processors import MapCompose,TakeFirst,Join
    from scrapy.loader import ItemLoaderclass ArticlespiderItem(scrapy.Item):# define the fields for your item here like:# name = scrapy.Field()pass#处理的函数,value表示input的值,即初始值
    def add_jobbole(value):return value+"-jobbole"#处理时间函数
    def date_convert(value):try:create_date = datetime.datetime.strptime(value, "%Y/%m/%d").date()except Exception as e:create_date = datetime.datetime.now().date()return create_date#获得字符串中数字
    def get_nums(value):match_re = re.match(".*?(\d+).*", value)if match_re:nums = int(match_re.group(1))else:nums = 0return nums#去掉含有评论的tag
    def remove_comment_tags(value):if "评论" in value:return ""else:return value#什么都不做的函数,专门用于覆盖default_output_processor
    def return_value(value):return valueclass JobBoleArticleItem(scrapy.Item):title = scrapy.Field(input_processor = MapCompose(add_jobbole) #Mapcompose表示可以对传入的内容调用多个函数进行预处理) #scrapy可以使用Field表示任意类型create_date = scrapy.Field(input_processor = MapCompose(date_convert),output_processor = TakeFirst() #使用TakeFirst函数可以只获得数组中的第一个)url = scrapy.Field()url_object_id = scrapy.Field() #对url进行编码front_image_url = scrapy.Field(output_processor = MapCompose(return_value))front_image_path = scrapy.Field()praise_nums = scrapy.Field(input_processor  =MapCompose(get_nums))comment_nums = scrapy.Field(input_processor=MapCompose(get_nums))fav_nums = scrapy.Field(input_processor=MapCompose(get_nums))tags = scrapy.Field(input_processor=MapCompose(remove_comment_tags), #tags中可能包含评论数,则需要定义一个函数去掉含评论的tagoutput_processor=Join(",") #使用Join函数连接数组中的内容,使用output_processor可以覆盖掉default_output_processor)content = scrapy.Field()#自定义itemloader
    class ArticleItemLoader(ItemLoader):#自定义itemloaderdefault_output_processor = TakeFirst()
    
  • pipelines.py调整内容,在ArticleImagePipeline中添加对item中是否有front_image_url的判断

    # -*- coding: utf-8 -*-
    #利用codecs打开文件
    import codecs
    import json
    import MySQLdb
    import MySQLdb.cursors
    # Define your item pipelines here
    #
    # Don't forget to add your pipeline to the ITEM_PIPELINES setting
    # See: https://doc.scrapy.org/en/latest/topics/item-pipeline.html
    from scrapy.pipelines.images import ImagesPipeline
    from scrapy.exporters import JsonItemExporter
    from twisted.enterprise import adbapiclass ArticlespiderPipeline(object):def process_item(self, item, spider):return item# 将结果存储成json
    class JsonWithEncodingPipeline(object):def __init__(self):# 利用codecs方式打开文件,与open函数不同在于编码,可以省去很多编码方面的繁杂工作self.file =  codecs.open("article.json","w",encoding="utf-8")def process_item(self, item, spider):#利用json.dumps函数将item存储成jsonlines = json.dumps(dict(item),ensure_ascii=False) + "\n"#写入json文件中self.file.write(lines)#process_item函数一定要返回item,因为下一个pipeline还会使用此itemreturn item#在spider close的时候关闭filedef spider_closed(self,spider):self.file.close()# 利用json exporter
    class JsonExporterPipeline(object):#调用scrapy提供的json exporter导出json文件def __init__(self):self.file = open('articleexporter.json','wb') # 二进制方式打开self.exporter = JsonItemExporter(self.file,encoding = "utf-8",ensure_ascii=False)self.exporter.start_exporting()def process_item(self, item, spider):self.exporter.export_item(item)return itemdef close_spider(self,spider):self.exporter.finish_exporting()self.file.close()# 继承ImagePipeline
    class ArticleImagePipeline(ImagesPipeline):def item_completed(self, results, item, info):if "front_image_url" in item:# result中包含一个tuple,一个是状态值,一个是返回对象for ok,value in results:image_file_path = value["path"]item["front_image_path"] = image_file_pathreturn item#Mysql pipeline
    class MysqlPipeline(object):def __init__(self):#连接mysqldb,创建连接self.conn = MySQLdb.connect('127.0.0.1','root','123456','scrapy',charset="utf8",use_unicode=True)#创建cursorself.cursor = self.conn.cursor()def process_item(self, item, spider):insert_sql = """insert into jobbole_article(title,create_date,url,url_object_id,front_image_url,front_image_path,comment_nums,fav_nums,praise_nums,tags,content)VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)"""self.cursor.execute(insert_sql,(item["title"],item["create_date"],item["url"],item["url_object_id"],item["front_image_url"],item["front_image_path"],item["comment_nums"],item["fav_nums"],item["praise_nums"],item["tags"],item["content"]))self.conn.commit()#异步mysql插入
    class MysqlTwistedPipeline(object):#setting中包含setting.py中的内容@classmethoddef from_settings(cls,settings):dbparms = dict(host = settings["MYSQL_HOST"],db = settings["MYSQL_DBNAME"],user = settings["MYSQL_USER"],passwd = settings["MYSQL_PASSWORD"],charset = "utf8",cursorclass = MySQLdb.cursors.DictCursor,use_unicode = True)#创建连接池,参数1使用的dbapi的name,dbpool = adbapi.ConnectionPool("MySQLdb",**dbparms)return cls(dbpool)def __init__(self,dbpool):self.dbpool = dbpooldef process_item(self, item, spider):#使用twisted将mysql插入变成异步执行,参数1表示要异步执行的函数,参数2表示执行的itemquery= self.dbpool.runInteraction(self.do_insert,item)query.addErrback(self.handle_error)#处理异常def do_insert(self,cursor,item):#执行具体的插入insert_sql = """insert into jobbole_article(title,create_date,url,url_object_id,front_image_url,front_image_path,comment_nums,fav_nums,praise_nums,tags,content)VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)"""cursor.execute(insert_sql, (item["title"], item["create_date"], item["url"], item["url_object_id"], item["front_image_url"],item["front_image_path"], item["comment_nums"], item["fav_nums"], item["praise_nums"], item["tags"],item["content"]))#不需要commit,会自动进行commitdef handle_error(self,failure):#处理异步插入的异常print(failure)
    
  • jobbole.py文件中通过itemloader处理item并进行传递

    # -*- coding: utf-8 -*-
    import scrapy
    import re
    from scrapy.http import Request
    from urllib import parse
    import datetime#引入item
    from ArticleSpider.items import JobBoleArticleItem, ArticleItemLoader
    #引入md5处理url
    from ArticleSpider.utils.common import get_md5class JobboleSpider(scrapy.Spider):name = 'jobbole'allowed_domains = ['blog.jobbole.com']start_urls = ['http://blog.jobbole.com/all-posts/'] #放入想爬取的所有urldef parse(self, response):"""1.获取文章列表页中的文章url并交给scrapy下载后并进行解析2.获取下一页url并交给scrapy进行下载,下载完成后交给parse:param response::return:"""#解析列表页的所有文章url并交给scrapy下载后进行解析#利用::attr()伪类选择器获得对应元素的属性值post_nodes = response.css("#archive .floated-thumb .post-thumb a")for post_node in post_nodes:image_url = post_node.css("img::attr(src)").extract_first("") #获得图片的srcpost_url = post_node.css("::attr(href)").extract_first("") #获得连接的href#利用Request方法提交请求获取对应url内容#url表示访问的url(parse.urljoin是借助urllib将当前url与请求url进行拼接,从而获取到真实的url),callback表示回调的函数yield Request(url=parse.urljoin(response.url,post_url),meta={"front_image_url":image_url},callback=self.parse_detail)#提交下一页并交给scrapy进行下载#两个类之间有空格则表示子元素,两个类中间没空格则表示同时满足两个类的元素next_url = response.css(".next.page-numbers::attr(href)").extract_first("")if next_url:yield Request(url=parse.urljoin(response.url, next_url), callback=self.parse)#提取文章的具体字段def parse_detail(self,response):######################## 利用css选择器获取内容####################### 实例化itemarticle_item = JobBoleArticleItem()# 获得meta中的front_image_url,文章封面图front_image_url = response.meta.get("front_image_url","")# 获得titletitle = response.css(".entry-header h1::text").extract()[0]print(title)create_date = response.css("p.entry-meta-hide-on-mobile::text").extract()[0].strip().replace("·", "").strip()# 点赞数praise_nums = response.css(".vote-post-up h10::text").extract()[0]# 收藏数collect = response.css('span.bookmark-btn::text').extract()[0]match_re = re.match(".*?(\d+).*", collect)if match_re:collect = match_re.group(1)else:collect = 0# 评论数comment = response.css('a[href="#article-comment"] span::text').extract()[0]match_re = re.match(".*?(\d+).*", comment)if match_re:comment = match_re.group(1)else:comment = 0# 获得文章内容content = response.css("div.entry").extract()[0]# 获得标签tags = response.css("p.entry-meta-hide-on-mobile a::text").extract()tag = [element for element in tags if not element.strip().endswith("评论")]tags = ",".join(tag)#填充itemarticle_item['title'] = title#判断是否有日期,有的话则格式化成对应格式,没有的话则是当前日期try:create_date = datetime.datetime.strptime(create_date,"%Y%m%d").date()except Exception as e:create_date = datetime.datetime.now().date()article_item['create_date'] = create_datearticle_item['url'] = response.urlarticle_item['url_object_id'] = get_md5(response.url)article_item['front_image_url'] = [front_image_url]article_item['praise_nums'] = praise_numsarticle_item['comment_nums'] = commentarticle_item['fav_nums'] = collectarticle_item['tags'] = tagsarticle_item['content'] = content#Item loader加载itemitem_loader = ArticleItemLoader(item=JobBoleArticleItem(),response=response)#使用css方式向item loader中填充值item_loader.add_css("title",".entry-header h1::text")item_loader.add_css("create_date","p.entry-meta-hide-on-mobile::text")item_loader.add_css("praise_nums",".vote-post-up h10::text")item_loader.add_css("comment_nums","a[href='#article-comment'] span::text")item_loader.add_css("fav_nums","span.bookmark-btn::text")item_loader.add_css("tags","p.entry-meta-hide-on-mobile a::text")item_loader.add_css("content","div.entry")#使用value方式向item_loader中填充值item_loader.add_value("url",response.url)item_loader.add_value("url_object_id",get_md5(response.url))item_loader.add_value("front_image_url",[front_image_url])article_item = item_loader.load_item()yield article_item #将item传递到pipeline中
    

Python爬虫爬取伯乐在线相关推荐

  1. Python爬虫爬取伯乐在线网站信息

    一.环境搭建 1.创建环境 执行pip install scrapy安装scrapy 使用scrapy startproject ArticleSpider创建scrapy项目 使用pycharm导入 ...

  2. Python scrapy爬虫爬取伯乐在线全部文章,并写入数据库

    伯乐在线爬虫项目目的及项目准备: 1.使用scrapy创建项目 2.创建爬虫,bole 域名 jobbole.com 3.Start_urls = ['http://blog.jobbole.com/ ...

  3. 毕设总结1:使用python scrapy 爬取 伯乐在线文章

    伯乐在线爬取教程 写在前面 scrapy架构 爬取过程 Spider 爬取思路 Item pipelines Main 写在前面 伯乐在线 好像已经不能访问了,但爬虫的思路还是一样的. scrapy架 ...

  4. python 爬虫-爬取学堂在线合作院校

    关键词:动态页面 JavaScript接口 绕过网页直接爬取信息 亲测有效,转自https://blog.csdn.net/weixin_43873801/article/details/109689 ...

  5. 边学边敲边记之爬虫系列(八):Scrapy系统爬取伯乐在线

    一.前言 上一篇边学边敲边记爬虫系列七给大家仔细讲解了如何用Xpath分类爬取医疗信息网站医疗器材名称和介绍图片,以及三种最常用的存储方法. 本篇是本系列的第八篇了,今天给大家讲讲如何用Scrapy分 ...

  6. scrapy 解析css,Scrapy基础(六)————Scrapy爬取伯乐在线一通过css和xpath解析文章字段...

    上次我们介绍了scrapy的安装和加入debug的main文件,这次重要介绍创建的爬虫的基本爬取有用信息 通过命令(这篇博文)创建了jobbole这个爬虫,并且生成了jobbole.py这个文件,又写 ...

  7. bs4主要知识点介绍及实例解析---利用bs4爬取伯乐在线(分别存储在数据库和xls表中)

    bs4主要知识点介绍及实例讲解 bs4 是第三方解析html数据的包 from bs4 import BeautifulSoup lxml 解析读取html的第三方解释器,解析速度快,底层通过c实现 ...

  8. scrapy简单入门 - 爬取伯乐在线所有文章

    推荐我的个人博客 http://blog.wuzhenyu.com.cn scrapy 是一个用 python 语言编写的,为了爬取网站数据,提取结构性数据而编写的应用框架. 环境 本文使用的环境: ...

  9. scrapy | 爬取伯乐在线全部博文(xpath/css/itemload三种提取方法,同步、异步方式存入MySQL)

    1.目标 伯乐在线网站地址:http://blog.jobbole.com/all-posts/ 爬取伯乐在线的所有文章信息,包括图片网址,标题,发表日期,标签,点赞数,评论数等 将爬取的数据保存至数 ...

最新文章

  1. 本地连接受限制或无法连接怎么办?
  2. INS-20802 PRVF-9802 PRVF-5184 PRVF-5186 After Successful Upgradeto 11gR2 Grid Infrastructure
  3. 51Nod- 1915 西湖游船
  4. Spring静态资源访问配置
  5. |NOIOJ|动态规划|3532:最大上升子序列和
  6. pbl和sbl_综合运用PBL和SBL教学法提高眼科带教质量的探索
  7. C语言常用字符串操作函数大全详解(strstr,strtok,strrchr,strcat,strcmp,strcpy,strerror,strspn,strchr等)
  8. linux目录结构与功能_深入理解linux系统的目录结构(总结的非常详细)
  9. 网络工程师(2) 操作系统
  10. 商业智能改变汽车行业
  11. qt.qpa.plugin: Could not load the Qt platform plugin xcb in /root/PycharmProjects/pythonPr
  12. 网络营销是中小企业低成本建立品牌的必经之路
  13. 300题 第七讲 零点定理与微分不等式
  14. 大数据BI工具Tableau学习【第三期】:数据类型,功能区和卡参考,文件类型(连接方式)
  15. 基于STM32HAL库ADC+DMA模式,高精度采集电池电量与芯片内部温度方法 (48脚 使用内部参考电压方案)
  16. android Codec-specific data
  17. PCA降维+SVR+数据可视化 实战记录
  18. java学习之面向对象和封装
  19. Spring中多语言页面适配方案总结
  20. python贪吃蛇控制台_通过 PRA 的 python-pygame 玩转贪吃蛇

热门文章

  1. NetSuite合作伙伴
  2. 一个庄稼汉的炒股心得
  3. 【Arduino】入门篇——火焰报警器
  4. 简单的数字变化:0-9循环
  5. What is OpenVZ
  6. python 自动点击鼠标左键和键盘
  7. 2021-2027全球与中国电子礼品卡市场现状及未来发展趋势
  8. 详解Unity的移动控制实现
  9. [Windows] exe 图标上的 UAC 盾牌
  10. android am pm常用的一些命令