摘要:此案例是爬取目标网站(https://tipdm.com/
)的新闻中心板块的公司新闻中所有新闻的标题、发布时间、访问量和新闻的文本内容。
我使用的是 Anaconda prompt
我们使用如下命令创建scrapy项目:scrapy startproject spider_name 爬虫路径
spider_name
是项目的名字
爬虫路径
就是项目所在位置
本案例内命令是:scrapy startproject mySpider 路径
我们先生成 spider(爬虫器)
文件:scrapy genspider spider_name domain
spider_name
:是生成的 爬虫器的名字,在目录spiders
下面domain
:是要爬取的网站的域名(一般是不加http,只写www.xxx.com就行)
本案例内,命令是scrapy genspider msd www.tipdm.com
指定要爬取的内容都有什么,我们在摘要中已经说明,要爬取的是 所有新闻的标题、发布时间、访问量和新闻的文本内容
。
所以有四个字段,代码如下:
import scrapy
class MyspiderItem(scrapy.Item):
title = scrapy.Field()
time = scrapy.Field()
view = scrapy.Field()
text = scrapy.Field()
我们要爬取的是:所有页面的、所有新闻的内容。
所以我们写三个parse
函数。
parse
:得到页数,并得到每页的网址,传给下个函数parse_url
:得到每个页面的所有新闻的链接parse_text
:分析得到的新闻界面的内容,并存入 item
中,返回回去注意:将 start_url
修改成第一个要爬取的页面,即公司新闻的页面
(代码中注释掉的内容都是调试用的)
先得到总页数,因为取出来的是 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)
得到单个页面中的所有新闻的网址,放在一个列表中
注意:这里被卡了好久,就是因为得到的网址是不完整的,没有前缀(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)
本函数是分析新闻的内容的函数,并存储在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
我们选择使用pipelines
存储,需要在settings
文件中取消相应代码注释
ITEM_PIPELINES = {
'mySpider.pipelines.MyspiderPipeline': 300,
}
因为我选择存储为.csv
文件,所以需要导入pandas
注意:to_csv
中,参数顺序不能乱
其中,参数的顺序如下:
filename
: 文件名,必需。index
: 是否写入行索引,可选。默认为False
。encoding
: 字符编码,可选。默认为utf-8-sig
。compression
: 压缩模式,可选。默认为gzip
。header
: 表头是否写入,可选。默认为None
。footer
: 表尾是否写入,可选。默认为None
。newline
: 换行符,可选。默认为None
。dtype
: 数据类型,可选。默认为None
。optimizer
: 优化器,可选。默认为None
。写入文件的写入器对象
: 写入文件的写入器对象,可选。默认为None
。写入文件的参数
: 写入文件的参数,可选。默认为None
。
需要注意的是,header
和footer
参数默认为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