摘要:此案例是爬取目标网站(https://tipdm.com/)的新闻中心板块的公司新闻中所有新闻的标题、发布时间、访问量和新闻的文本内容。

1. 创建scrapy项目

我使用的是 Anaconda prompt 我们使用如下命令创建scrapy项目:scrapy startproject spider_name 爬虫路径

spider_name是项目的名字
爬虫路径就是项目所在位置

本案例内命令是:scrapy startproject mySpider 路径

2. 生成爬虫器

我们先生成 spider(爬虫器) 文件:scrapy genspider spider_name domain

  1. spider_name:是生成的 爬虫器的名字,在目录spiders 下面
  2. domain:是要爬取的网站的域名(一般是不加http,只写www.xxx.com就行)

本案例内,命令是scrapy genspider msd www.tipdm.com

3. 指定爬取字段(items.py)

指定要爬取的内容都有什么,我们在摘要中已经说明,要爬取的是 所有新闻的标题、发布时间、访问量和新闻的文本内容
所以有四个字段,代码如下:

import scrapy

class MyspiderItem(scrapy.Item):
    title = scrapy.Field()
    time = scrapy.Field()
    view = scrapy.Field()
    text = scrapy.Field()

4. 编写爬虫文件(msd.py)

我们要爬取的是:所有页面的、所有新闻的内容。
所以我们写三个parse函数。

  1. parse:得到页数,并得到每页的网址,传给下个函数
  2. parse_url:得到每个页面的所有新闻的链接
  3. parse_text:分析得到的新闻界面的内容,并存入 item 中,返回回去

注意:将 start_url 修改成第一个要爬取的页面,即公司新闻的页面

(代码中注释掉的内容都是调试用的)

parse函数

先得到总页数,因为取出来的是 str,将其转成int类型。
通过取出各个页面可知,每个页面的网址类似,只有数字不同,我们就可以生成网址了。
然后使用 yield Request() 回调下一个函数。
现在还不太懂原理。yield ,并不会直接返回,而是会返回一个生成器,可以遍历完所有的网页再统一返回

def parse(self, response):
        number = int(response.xpath('//*[@id="t251"]/div[6]/div/a[6]/text()').extract()[0]) # 总页数
        url_all = ['http://www.tipdm.com/gsxw/index_{}.jhtml'.format(i) for i in range(1, number + 1)]
        
        for i in url_all:
            yield Request(url=i, callback=self.parse_url, dont_filter=True)

parse_url

得到单个页面中的所有新闻的网址,放在一个列表中
注意:这里被卡了好久,就是因为得到的网址是不完整的,没有前缀(http之类的)一直进不到下一个函数(可以在每个函数写一个输出语句,以便检查是否进入了函数)
所以我们将url补充完毕,进行回调
爬取不出来的时候,一定要输出一下网址看看

def parse_url(self, response):
        urls = response.xpath('//*[@id="t251"]/div/div[3]/h1/a/@href').extract()
        for i in urls:
            yield Request(url=('http://www.tipdm.com' + i), callback=self.parse_text, dont_filter=True)

parse_text

本函数是分析新闻的内容的函数,并存储在item中返回
有个很尴尬的点是这个网站的前端有点乱,有几条新闻的文本内容的xpath路径和其他的都不一样,好麻烦的,现在还不知道怎么解决

def parse_text(self, response):
        item = MyspiderItem()
        item['title'] = response.xpath('/html/body/div[2]/div/div[1]/div[2]/h1/text()').extract()  # title
        item['time'] = response.xpath('/html/body/div[2]/div/div[1]/div[2]/div/div[1]/span[1]/text()').extract()  # time
        item['view'] = response.xpath('/html/body/div[2]/div/div[1]/div[2]/div/div[1]/span[3]/text()').extract()  # view
        item['text'] = '\n'.join(response.xpath('/html/body/div[2]/div/div[2]/p/text()').extract())
        if item['text'] == '':
            item['text'] = '\n'.join(response.xpath('/html/body/div[2]/div/div[2]/p/span/text()').extract())
            if item['text'] == '':
                item['text'] = '\n'.join(response.xpath('/html/body/div[2]/div/div[2]/section[2]/section/section[2]/section[2]/p/span/text()').extract())
        return item

完整代码

import scrapy
from scrapy.http import Request
from mySpider.items import MyspiderItem


class MsdSpider(scrapy.Spider):
    name = 'msd'
    allowed_domains = ['www.tipdm.com']
    start_urls = ['http://www.tipdm.com/gsxw/index.jhtml']

    def parse(self, response):
        number = int(response.xpath('//*[@id="t251"]/div[6]/div/a[6]/text()').extract()[0]) # 总页数
        url_all = ['http://www.tipdm.com/gsxw/index_{}.jhtml'.format(i) for i in range(1, number + 1)]

        for i in url_all:
            # print('1111111111111111')
            yield Request(url=i, callback=self.parse_url, dont_filter=True)
            # print(i)

    def parse_url(self, response):
        urls = response.xpath('//*[@id="t251"]/div/div[3]/h1/a/@href').extract()
        # print('22222222222222222222')
        for i in urls:
            yield Request(url=('http://www.tipdm.com' + i), callback=self.parse_text, dont_filter=True)

    def parse_text(self, response):
        # print('77')
        item = MyspiderItem()
        # print('78')
        item['title'] = response.xpath('/html/body/div[2]/div/div[1]/div[2]/h1/text()').extract()  # title
        # print('7999')
        item['time'] = response.xpath('/html/body/div[2]/div/div[1]/div[2]/div/div[1]/span[1]/text()').extract()  # time
        item['view'] = response.xpath('/html/body/div[2]/div/div[1]/div[2]/div/div[1]/span[3]/text()').extract()  # view
        item['text'] = '\n'.join(response.xpath('/html/body/div[2]/div/div[2]/p/text()').extract())
        if item['text'] == '':
            item['text'] = '\n'.join(response.xpath('/html/body/div[2]/div/div[2]/p/span/text()').extract())
            if item['text'] == '':
                item['text'] = '\n'.join(response.xpath('/html/body/div[2]/div/div[2]/section[2]/section/section[2]/section[2]/p/span/text()').extract())
        # print('79')
        return item

5. 存储数据(pipelines.py、settings.py)

我们选择使用pipelines存储,需要在settings文件中取消相应代码注释

settings.py

ITEM_PIPELINES = {
   'mySpider.pipelines.MyspiderPipeline': 300,
}

pipelines.py

因为我选择存储为.csv文件,所以需要导入pandas注意to_csv中,参数顺序不能乱

其中,参数的顺序如下:

  1. filename: 文件名,必需。
  2. index: 是否写入行索引,可选。默认为False
  3. encoding: 字符编码,可选。默认为utf-8-sig
  4. compression: 压缩模式,可选。默认为gzip
  5. header: 表头是否写入,可选。默认为None
  6. footer: 表尾是否写入,可选。默认为None
  7. newline: 换行符,可选。默认为None
  8. dtype: 数据类型,可选。默认为None
  9. optimizer: 优化器,可选。默认为None
  10. 写入文件的写入器对象: 写入文件的写入器对象,可选。默认为None
  11. 写入文件的参数: 写入文件的参数,可选。默认为None
    需要注意的是,headerfooter参数默认为None,表示不写入表头和表尾。如果希望写入表头或表尾,可以设置该参数为True
import pandas as pd
from itemadapter import ItemAdapter

class MyspiderPipeline:
    def process_item(self, item, spider):
        data = pd.DataFrame(dict(item))
        data.to_csv('data2.csv', mode='a+', index=None, encoding='utf-8-sig', header=None)
        return item