构造分布式Scrapy_redis爬虫,爬取新浪新闻sina整站的新闻文章-day1
爬虫第一步:新建项目
- 选择合适的位置,执行命令:**scrapy startproje sinaNews**(sinaNews是自定义爬虫项目名称) 成功执行后,**如图**: 爬虫第二步:明确目标
- 首先打开要爬取的网站:https://news.sina.com.cn/ 在**导航div(main-nav)**中发现有**15个父类**,图片中**颜色深的是父类标题**  - 由于在15个父类中包含的不止三个子类,所以在**此页面取到父类的url**后发送Request请求,获得父类中**所有子类的url** 如:点进**父类:财经**后,有**如下**这些**子类标题**  - 所有子类点进去后,就可以找到每篇文章的url,拿到文章的url后,就可以文章的标题和内容了(由于有的子类加载数据的方式不同,本文中的方法可能不适用所有的子类页面结构,后续会慢慢更新和维护,在此先写一个公共的方法) - 分析后,明确了我们需要获取的**item字段**: **item["parentUrls"]**:文章所属父类的url **item["subUrls"]**:文章所属子类的url **item["newsUrl"]**:文章本身的url **item["newsTitle"]**:文章标题 **item["newsContent"]**:文章内容用xpath在页面中提取我们需要的数据
-
item["parentUrls"]:在start_urls中获取,xpath规则://div[@class="main-nav"]//ul/li[1]/a/@href
![]()
如图,多了两个错误的url,在代码中用切片方法切掉就可以了。 -
item["subUrls"]:在父类页面中获取,由于每个父类的页面结构都不同,所以需要不同的xpath规则来获取子类url
如:在财经页面:子类url提取的xpath规则://div[@class="m-nav"]//a/@href
![]()
-
item["newsUrl"]:在子类页面中获取,由于文章的url都是有.html结束的,所以我采用的是在子类页面中获取所有的连接://li//a/@href,然后在代码中用endwith(.html)取出文章的url
-
item["newsTitle"]:在文章页面获取,大多数文章页面的文章标题提取规则都差不多,xpath规则://h1[@class="main-title"]
![]()
-
item["newsContent"]:在文章页面获取,xpath规则://div[@class="article"]//p
![]()
-
确认完item字段后,编写items.py文件
import scrapy
class SinanewsItem(scrapy.Item):
# 由于后期要做分布式爬取,所以多加了两个字段(crawled,spider),方便区分数据的来源和时间
# 父类url
parentUrls = scrapy.Field()
# 子类url
subUrls = scrapy.Field()
# 每条新闻的url
newsUrls = scrapy.Field()
# 每条新闻的标题
newsTitle = scrapy.Field()
# 每条新闻的内容
newsContent = scrapy.Field()
# 爬取时间
crawled = scrapy.Field()
# 爬取数据的spider名
spider = scrapy.Field()
爬虫第三步:编写爬虫文件spider
- 本文使用spider类编写,进入sinaNews目录,执行命令:**scrapy genspider sina "sina.com.cn"** 其中**sina是爬虫名**,不能和爬虫项目名重复,"sina.cn"爬虫限制的**域名**,**限制爬虫的范围** - 执行成功后,在spider目录先会出现sina.py文件,打开他编写自己的爬虫程序 ~~~ import scrapy class SinaSpider(scrapy.Spider): name = 'sina' allowed_domains = ['sina.com'] start_urls = ['http://sina.com/'] # 此方法是spider类爬虫必须写的,用来解析第一个(start_urls)页面 def parse(self, response): pass ~~~ **写入自己的逻辑**:import scrapy
# 从items.py文件中导入SinanewsItem类
from sinaNews.items import SinanewsItem
class SinaSpider(scrapy.Spider):
name = 'sina'
# 允许爬虫的范围
allowed_domains = ['sina.com.cn']
# 改为sina新闻的首页url
start_urls = ['http://news.sina.com.cn/']
# 此方法是自动调用的方法,用来解析start_urls页面结构
def parse(self, response):
# 创建一个空的items列表,为了数据的传递
items = []
# 所有大类的url 共17个 发现多了两个,用切片方法把它切掉
parentUrls = response.xpath('//div[@class="main-nav"]//ul/li[1]/a/@href').extract()[:-2]
# 遍历15个url
for i in range(0,len(parentUrls)):
# 实例化item一个item对象,用来保存字段数据
item = SinanewsItem()
# 保存父类url
item["parentUrls"]=parentUrls[i]
# 添加到items列表中
items.append(item)
# 遍历列表
for item in items:
# 将15个父类的请求Request交给scheduler调度器入队列,meta保存item信息传给回调函数,回调函数second_parse是用来处理父类请求的Response
yield scrapy.Request(url=item["parentUrls"],meta={"meta_1":item},callback=self.second_parse)
# 用来解析15父类请求返回的页面,由于每个页面的结构不同,所以需要写15个方法来处理(这里我用了一个比较笨的方法,因为初学,经验不足,但是我相信肯定有更好的方法,欢迎大家评论)
def second_parse(self,response):
items=[]
# 拿到返回页面的url,根据url的不同,进行不同的处理方法
if response.url == "https://news.sina.com.cn/":
# 跟进上面分析的xpath规则,取到子类的url
subUrls = response.xpath('//div[@class="cNav2"]//a/@href').extract()
# 遍历解析到的子类url
for i in range(0,len(subUrls)):
item = SinanewsItem()
# 将数据存到item里面
item["parentUrls"] = response.url
item["subUrls"] = subUrls[i]
items.append(item)
for item in items:
# 将子类url的请求再入到请求队列,meta传递数据,回调函数news_parse用来解析所有子类请求返回的页面
yield scrapy.Request(url=item["subUrls"],meta={"meta_2":item},callback=self.news_parse)
# 这里先写两个父类页面的解析,其实都差不多,只是提取子类url的xpath规则有所改变,其它的都一样
elif response.url == 'https://finance.sina.com.cn/':
subUrls = response.xpath('//div[@class="m-nav"]//a/@href').extract()
for i in range(0,len(subUrls)):
item = SinanewsItem()
item["parentUrls"] = response.url
item["subUrls"] = subUrls[i]
items.append(item)
for item in items:
# 所有的子类页面都调用news_parse来处理结果
yield scrapy.Request(url=item["subUrls"],meta={"meta_2":item},callback=self.news_parse)
# 这个方法是用来提取每个子类页面中的每一篇文章的url
def news_parse(self,response):
# 提取上个方法传递的item数据
meta = response.meta["meta_2"]
items = []
# 提取页面中所有的url连接
urls = response.xpath('//li//a/@href').extract()
for url in urls:
# 遍历提取以.html结尾的url
if url.endswith(".html"):
item = SinanewsItem()
# 存储item数据
item["newsUrls"] = url
item["parentUrls"] = meta["parentUrls"]
item["subUrls"] = meta["subUrls"]
items.append(item)
for item in items:
# 再将所有文章的url请求入队列,回调函数gov_parse来处理文章页面
yield scrapy.Request(url=item["newsUrls"],meta={"meta_3":item},callback=self.gov_parse)
# 这个方法用来处理每个文章的url,因为文章页面的结构大多都差不多,所以写一个统一的方法,如果有不同的页面,也可以写不同的方法来处理
def gov_parse(self,response):
# 获取传递的数据
meta = response.meta["meta_3"]
# 实例化
item = SinanewsItem()
# 获取每一篇文章标题
title = response.xpath('//h1[@class="main-title"]/text()').extract()
# 获取每一篇文章类容
content = response.xpath('//div[@class="article"]//p/text()').extract()
# 判断是否有取到内容,以防程序报错
if len(content) != 0 and len(title) != 0:
item["parentUrls"] = meta["parentUrls"]
item["subUrls"] = meta["subUrls"]
item["newsUrls"] = meta["newsUrls"]
item["newsTitle"] = title[0]
# 由于取到的文章内容是一个列表,用join方法将列表转为字符串
new_content = "\n".join(content)
item["newsContent"] = new_content
# 将取到的数据发给pipeline来处理
yield item
爬虫第四步:存储内容,编写管道文件pipeline.py
-由于是是构造**分布式爬虫**,所以用的**scrapy_redis的管道**来处理,但是我们可以让数据先流过我们自己写的管道,所以可以**加两个字段**from datetime import datetime
class SinanewsPipeline(object):
# 当数据流过我们的管道时,加上crawled和spider
def process_item(self, item, spider):
# 格林威治时间,+8就是北京时间
item["crawled"] = datetime.utcnow()
# 存储当前爬虫的爬虫名
item["spider"] = "snia"
return item
到目前为止,爬取新浪新闻所有文章的代码差不多写完了,以上代码基本上拿过去就可以用(前提是sinau页面结构没有改动),但是让整个程序跑起来,还需要修改seeting.py文件





浙公网安备 33010602011771号