使用CrawlSpider轻松爬取巴比特网全站数据

     鉴于森总之前给自己布置的一个小demo,趁晚上的时间总结一下,欢迎拍砖~

     当需要爬取全站的文章数据时,我们会想到用:

      1.lxml解析器;2.用BeautifulSoup库提取;3.或者用Scrapy框架再用Selector选择器进行选择

     但是这里有一个更好的爬取全站数据的方法,即使用CrawSpider;

      CrawSpider的使用特点在于它那强大的神器LinkExtractor,来制定特定规则将其不是想抓取的东西筛选掉,以此来达到抓取到自己想要的数据的目的;现在让笔者带你一步步的爬取某特网文章的数据吧!

一.项目需求

     1.1爬取每一页上的每一条文章的标题.作者,以及发布时间;

     1.2爬取每一篇文章内容的详情;

     1.3爬取的内容以某种类型的方式进行存储

二.页面分析

     2.1对每一页的内容进行分析,分析页面如下:

     













     2.2对每一篇的内容进行分析,分析页面如下: 


三.实现

3.1  在对每一页的内容进行分析时,关键是如何让其一直跳转到下一页直至最后,关于这个常见的爬取操作,我有以下4种方案供参考:

      1.使用xpath定位到下一页所在的列表框链接处,每次提取到下一页的这个位置,从而提取出元素:

 a_xpath ='//*[@id="zan-bodyer"]/div/div/div/div[2]/div[1]/div/div/div/ul/li'
    lis = html.xpath(a_xpath)
      注:但这个方法的麻烦之处在于->第一页中所包含的有"'下一页','尾页'",但到了第二页后就有了"'首页','上一页','下一页','尾页'",到了最后一页中,由于文章可能不满20篇,所用xpath选到的li的行数也会出现变化,从而没能提取到所需要的数据;

   tip->因为尾页的链接并不能直接指向尾页,笔者在这里用了2分法的方法来查找到了最后一页,从而观察到了最后一页的格式;

    2.通过观察每一页的不同,发现每一页的数据中的区别仅在于pg后面的数字的不同:http://www.8btc.com/sitemap?pg=?;由此笔者在然导的帮助下,使用了传入index,并利用index每次循环加1,并在最后判断,如果提取的内容的长度为0,便break,由此得到了每一页的xpath中的数据,具体实现请参考笔者的另一篇《利用lxml爬取巴比特全站数据》;

   3.通过Scrapy框架的Selector选择器的方法,从而匹配到该位置的数据,具体实现如下:

next_url = Selector(response).re(r'下一页')
   4.通过CrawSpider的rule正则规则来匹配到想要的数据(比较简便):
rules = (
        Rule(LinkExtractor(allow=r'.+sitemap\?cat=&pg=\d'), callback='parse_item', follow=True),
    )
 笔者在这篇文章中用的是CrawSpider,因此本文中用的是第四种方法进行实现;

基本步骤:

 1.创建项目:

scrapy startproject 爬虫名字
 2.创建爬虫:
scrapy genspider -t crawl 爬虫名字 域名
 3.对settings.py进行设置:
ROBOTSTXT_OBEY = False
DOWNLOAD_DELAY = 2
DEFAULT_REQUEST_HEADERS = {
  'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
  'Accept-Language': 'en',
  'User-Agent':'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36'
}
ITEM_PIPELINES = {
   'btc_CrawSpiderDemo.pipelines.BtcCrawspiderdemoPipeline': 300,
}
4.使用Scapy的Rule进行规则设计:
rules = (
        Rule(LinkExtractor(allow=r'.+sitemap\?cat=&pg=\d'), callback='parse_item', follow=True),
    )
  注:此处务必要注意?为原生字符,需要使用\?来进行转义
5.设计parse_item的方法进行返回的操作:
btcdivs = response.xpath('//*[@id="zan-bodyer"]/div/div/div/div[2]/div[1]/div/div/div/ul/li')
        for btcdiv in btcdivs:
            title = btcdiv.xpath("./a/@title").get()
            author = btcdiv.xpath("./p/span/a/text()").get()
            time = btcdiv.xpath("./p/span/text()").get()
6.在pipeline的文件中,对爬取到的数据使用json来保存,并且规定好爬虫的开始,进行,和结束:
from scrapy.exporters import JsonLinesItemExporter
class BtcCrawspiderdemoPipeline(object):
    def __init__(self):
        self.fp = open("article.json",'wb')
        self.exporter = JsonLinesItemExporter(self.fp,ensure_ascii=False,encoding='utf-8')

    def open_spider(self,spider):
        print("爬虫开始了...")
    def process_item(self, item, spider):
        self.exporter.export_item(item)
        return item
    def close_spider(self,spider):
        self.fp.close()
        print("爬虫结束了...")
7.使用Items来封装数据,而并非使用字典,在Items.py中实现此方法:

import scrapy
class BtcCrawspiderdemoItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    title = scrapy.Field()
    author = scrapy.Field()
    time = scrapy.Field()
8.在原来的spiders中使用Items类来封存数据,如下:
 item = BtcCrawspiderdemoItem(title=title, author=author, time=time)
            yield item
最后产生json文件结果如下:

  因为需要爬取的页数太多,因此只让其显示到了100多条数据;

3.2  在文章的内容爬取时,发现了这样一个问题,如果只对文章内容进行直接抓取时,会需要再定义一个Rule来转向一个方法,这就需要再一个页面for循环中再加入一个的for循环来进入到每一个文章详情页来遍历,为了节省开销,因此笔者决定直接对详情页进行遍历,从而从中直接获取到文章名,作者,发表时间,文本内容,图片信息,并将此信息通过Items封装起来,如下:


    按上述的方法,对页面制定规则,因为文章的详情页属于二级页面,因此需要对其进行制定两个规则,即:

     定义规则后,相当于是按照规则进行递归,首先是第一个规则指向的第一页,接着follow到第二个规则,通过第二个规则遍历每一条文章,从而遍历到每一篇文章的链接;

     小tip:在刚拿到这个链接时,先不要急着xpath指定位置的数据,先通过输出response.text看是否会返回出结果

     拿到每一篇文章的链接后,接下来就对这个链接的xpath中各个部分进行提取,从而提取出需要的数据:

title = response.xpath('//*[@id="zan-bodyer"]/div/div/div[1]/article/div[3]/h1/text()').extract()
        author = response.xpath('//*[@id="zan-bodyer"]/div/div/div[1]/article/div[4]/span[2]/a/text()').extract()
        time = response.xpath('//*[@id="zan-bodyer"]/div/div/div[1]/article/div[4]/span[3]/time/text()').extract()
        content_xpath = response.xpath('//*[@id="zan-bodyer"]/div/div/div[1]/article/div[5]')
       
        content = content_xpath.xpath("./p/text()").getall()
        content = "".join(content).strip()
        img = content_xpath.xpath("./p/a/@href").get()
   小tip->在提取文本的content时,因为文本内容分了好多段,为了避免存储时,分成了许多字符串,采用join的方法对其进行连接再去除空格:"".join(content).strip()

   最后将数据封装,键值对方式的字典存储是我们首先想到的方式,但是字典的方式很容易出现:

       1.不方便携带元数据,传递给其他组件的信息;

       2.无法一目了然的了解数据中包含哪些字段,影响代码的可读性;

   因此我们在Scrapy使用自定义的Item类来封装爬取到的数据,同3.1,我们对数据进行存储->

item = BtcCrawspiderdemoItem(title=title, author=author, time=time,content=content,img=img)
        article = {"title":title,"author":author,"time":time,"content":content,"img":img}
        yield item
     并在pipeline.py中将爬取到的数据保存为json文件:
from scrapy.exporters import JsonLinesItemExporter
class BtcCrawspiderdemoPipeline(object):
    def __init__(self):
        self.fp = open("article.json",'wb')
        self.exporter = JsonLinesItemExporter(self.fp,ensure_ascii=False,encoding='utf-8')

    def open_spider(self,spider):
        print("爬虫开始了...")
    def process_item(self, item, spider):
        self.exporter.export_item(item)
        return item
    def close_spider(self,spider):
        self.fp.close()
        print("爬虫结束了...")

    最后我们运行下spider,看结果是否能显示,运行spider的基本方法是通过在文件的路径下运行shell的命令:scrap crawl 爬虫名字;但考虑到每次通过shell运行太过繁琐,就在源目录下创建了一个start文件,在pycharm中直接进行运行,如下:

最后运行结果保存在json文件如下:

   

   成功将所有数据都爬取并保存到了json文件中。

四.反思

    1.有一个小细节,在数据出来时,其得到每页数据的顺序和原来的顺序不一致,难道是rule规则不是递归执行的?实际上是因为在遍历时,py不关心键值对的存储顺序,而只去跟踪键值之间的关系;

     2.验证码,登陆,分布式,ip代理还需多总结;

     欢迎拍砖~







                                                

你可能感兴趣的:(Python爬虫)