Scrapy项目总结

第一步:创建项目

需要用到虚拟环境的话,可以按照网上教程自行配置。

我们跳过这一步,首先在桌面创建一个合适的文件夹并命名为自己想取的名字

第二步:使用命令终端,然后cd到桌面我们创建的文件夹。

第三步:在该文件夹目录下创建项目,运行命令:scrapy startproject myApp(根据自己项目命名)

接下来我们就可以打开pycharm查看项目的目录,可以看到如下结构:

(base) MACdeMBP:ArticleSpider mac$ tree
.
 |____ArticleSpider
 | |____spiders
 | | |______init__.py
 | | |____jobbole.py
 | | |______pycache__
 | | | |____jobbole.cpython-37.pyc
 | | | |______init__.cpython-37.pyc
 | |______init__.py
 | |______pycache__
 | | |____settings.cpython-37.pyc
 | | |______init__.cpython-37.pyc
 | |____middlewares.py
 | |____settings.py
 | |____items.py
 | |____pipelines.py
 |____scrapy.cfg
 |____main.py

现在我们来理解项目的整体目录结构,以及每个py文件的作用。

1,jobbole.py:爬虫项目中主要负责网页中字段和url提取和解析

2,items.py:负责jobbole中提取数据的处理,这里我们可以引入Itemloader类,ItemLoader 是分离数据的另一种方式,可以将数据的分离和提取分为两部分, 默认使用xpath,css数据提取方式,让代码更加整洁,更加清晰。

3,pipelines.py:用于存储爬取的数据。

4,middlewares.py:下载器中间件,作用:更换代理IP,更换Cookies,更换User-Agent,自动重试

5,settings.py:配置文件。

6,main.py:这是我自己创建方便运行管理的文件

接下来我们认识一下每一个py文件里所做的内容:

jobbole.py

# -*- coding: utf-8 -*- 
  import scrapy 
  import re,time,datetime 
  from scrapy.http import Request 
  from urllib import parse 
  from ArticleSpider.items import JobBoleArticleItem 
  from ArticleSpider.utils.common import get_md5 
    
    
  class JobboleSpider(scrapy.Spider): 
      name = 'jobbole' 
      allowed_domains = ['blog.jobbole.com'] 
      start_urls = ['http://blog.jobbole.com/all-posts/'] 
    
      def parse(self, response): 
          ''' 
          1,获取文章列表页中的文章url并交给解析函数进行具体字段的解析 
          2,获取下一页的url并交给scrapy进行下载,下载完成后交给parse函数 
          ''' 
          # 解析列表页中的所有文章url交给解析函数进行具体字段的解析 
          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('') 
              post_url = post_node.css("::attr(href)").extract_first('') 
              #url拼接好后使用yield关键字返回给scrapy进行下载 
              yield Request(url=parse.urljoin(response.url,post_url),meta={"front_image_url":image_url},callback=self.parse_detail) 
    
          # 提取下一页并交给scrapy进行下载 
          time.sleep(1) 
          next_url = response.css(".next.page-numbers::attr(href)").extract_first('') 
          if next_url: 
              yield Request(url=parse.urljoin(response.url,post_url),callback=self.parse) 
    
      def parse_detail(self,response): 
          ''' 
          处理每个url的方法,解析具体文章的逻辑 
          提取文章的具体字段 
          ''' 
          article_item = JobBoleArticleItem() 
          #文章封面图 
          front_image_url = response.meta.get('front_image_url','') 
          #提取文章标题 
          title = response.xpath('//div[@class="entry-header"]/h1/text()').extract()[0] 
          #提取日期 
          create_date = response.xpath('//p[@class="entry-meta-hide-on-mobile"]/text()').extract()[0].strip().replace(' ·','') 
          #提取点赞数目 
          praise_nums = response.xpath('//span[contains(@class,"vote-post-up")]/h10/text()').extract()[0] 
          #提取收藏数目(^(\d)* 匹配任意数字 
          fav_nums = response.xpath('//span[contains(@class,"bookmark-btn")]/text()').extract()[0].strip() 
          match_re = re.match(r'^(\d)*',fav_nums) 
          if match_re: 
              fav_nums = match_re.group() 
          else: 
              fav_nums = 0 
          #提取文章评论数 
          #comment = response.xpath('//a[@href="#article-comment"]/text()').extract()[0].strip() 
          comment = response.css('a[href="#article-comment"] span::text').extract()[0].strip() 
          match_re = re.match(r'^(\d)*',comment) 
          if match_re: 
              comment = match_re.group() 
          else: 
              comment = 0 
          #提取网页文章的内容 
          content = response.xpath('//div[@class="entry"]').extract()[0] 
          #这一步主要为了提取文章的所属类型 
          tag_list = response.xpath('//p[@class="entry-meta-hide-on-mobile"]/a/text()').extract() 
          tag_list = [element for element in tag_list if not element.strip().endswith("评论")] 
          tags = ','.join(tag_list) 
    
    
          article_item["url_object_id"] = get_md5(response.url) 
          article_item["title"] = title 
          article_item["url"] = response.url 
          #转换时间格式,由取得的字符串处理成时间格式,方便存在数据库中 
          try: 
              create_date = datetime.datetime.strftime(create_date,"%Y/%m/%d").date() 
          except Exception as e: 
              create_date = datetime.datetime.now().date() 
          article_item["create_date"] = create_date 
          article_item["front_image_url"] = [front_image_url] 
          article_item["praise_nums"] = praise_nums 
          article_item["fav_nums"] = fav_nums 
          article_item["comment"] = comment 
          article_item["tags"] = tags 
          article_item["content"] = content 
          yield article_item 
    
    
          #print(title,create_date,praise_nums,fav_nums,comment,tags,front_image_url) 
  

这里我们定义了一个md5加密函数,这个函数是后来添加的文件utils文件夹中的,它是项目里面的一个包,里面定义了md5解密的函数,可以使用from ArticleSpider.utils.common import get_md5来应用这个方法。字段介绍在代码中已经有具体注释,这里我们以伯乐在线网站为范例。这个py文件主要负责url解析,以及关键字段提取。下面是md5加密函数

import hashlib 
  def get_md5(url): 
      if isinstance(url,str): 
          url = url.encode('utf-8') 
      m = hashlib.md5() 
      m.update(url) 
      return m.hexdigest() 
  if __name__ == '__main__': 
      print(get_md5("http://jobbole.com".encode('utf-8'))) 

python3中所有的str类型可以看做是unicode编码。在python2中,就不需要使用解码了。

pipeline.py定制管道。可以用它来定制自己保存图片的路径,还可以使用它和数据库进行交互,下面与数据库mysql交互用到了两种方法,同步和异步,异步框架twisted中已经给我们定制好了异步机制,和处理异步带来的可能出错的处理,这个可以参照它来写。具体如下。异步可以防止大量数据存储和爬虫速度不同步问题。

# -*- 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 
  import codecs,json 
  import MySQLdb 
  import MySQLdb.cursors 
  from scrapy.pipelines.images import ImagesPipeline 
  from scrapy.exporters import JsonItemExporter 
  from twisted.enterprise import adbapi 
    
  class ArticlespiderPipeline(object): 
      def process_item(self, item, spider): 
          return item 
    
    
  class JsonWithEncodingPipeline(object): 
      # 自定义导出json文件 
      def __init__(self): 
          # json文件保存和格式 
          self.file = codecs.open('article.json','w',encoding='utf-8') 
      def process_item(self,item,spider): 
          #调用json.dumps方法以字典形式保存数据 
          lines = json.dumps(dict(item),ensure_ascii=False) 
          self.file.write(lines) 
          return item 
      def spider_close(self,spider): 
          self.file.close() 
    
    
  class JsonExporterPipeline(object): 
      #调用scrapy提供的json_export导出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 close_spider(self,spider): 
          self.exporter.finish_exporting() 
          self.file.close() 
    
      def process_item(self,item,spider): 
          self.exporter.export_item(item) 
          return item 
    
    
  class MysqlPipeline(object): 
      ''' 
      采用同步的机制插入数据库 
      ''' 
      def __init__(self): 
          self.conn = MySQLdb.connect('localhost','root','maple','test2',charset='utf8',use_unicode=True) 
          self.cursor = self.conn.cursor() 
    
      def process_item(self,item,spider): 
          insert_sql = """ 
              insert into jobbole_article(title,url,create_date,fav_nums,url_object_id, 
              front_image_url,front_image_path,comment,praise_nums,tags,content) 
              VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) 
         """ 
          self.cursor.execute(insert_sql,(item['title'],item['url'],item['create_date'],item['fav_nums'], 
                                          item['url_object_id'],item['front_image_url'],item['front_image_path'], 
                                          item['comment'],item['praise_nums'],item['tags'],item['content'])) 
          self.conn.commit() 
    
    
    
  class MysqlTwistedPipeline(object): 
      ''' 
      twisted 异步api写入数据到数据库 
      ''' 
      def __init__(self,dbpool): 
          self.dbpool = dbpool 
    
      @classmethod 
      def from_settings(cls,settings): 
          #从settings文件中引入mysql基础配置 
          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, 
          ) 
          dbpool = adbapi.ConnectionPool("MySQLdb",**dbparms) 
          return cls(dbpool) 
    
      def process_item(self,item,spider): 
          # 使用twisted将mysql插入变成异步插入 
          query = self.dbpool.runInteraction(self.do_insert,item) 
          query.addErrback(self.handle_error) 
    
      def handle_error(self,failure): 
          # 处理异步插入的异常 
          print(failure) 
    
    
      def do_insert(self,cursor,item): 
          # 执行具体插入 
          insert_sql = """ 
                     insert into jobbole_article(title,url,create_date,fav_nums,url_object_id, 
                     front_image_url,comment,praise_nums,tags,content) 
                     VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) 
                """ 
          cursor.execute(insert_sql, (item['title'], item['url'], item['create_date'], item['fav_nums'], 
                                           item['url_object_id'], item['front_image_url'], 
                                           item['comment'], item['praise_nums'], item['tags'], item['content'])) 
    
    
    
  class ArticleImagePipeline(ImagesPipeline): 
      ''' 
      处理图片,保存在自己设置的path 
      ''' 
      def item_completed(self, results, item, info): 
          for ok,value in results: 
              image_file_path = value['path'] 
          item['front_image_path'] = image_file_path 
          return item 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值