引言
数据的增量抓取指的是定期抓取新产生的数据,对于新闻网站而言,这意味着仅抓取自上次抓取后新发布的新闻。在本教程中,我会指导你如何使用Scrapy框架实现对一个新闻网站的增量爬取。
为什么选择增量爬取?
- 效率高:仅抓取新数据,节约资源。
- 速度快:减少不必要的请求和处理。
- 适应性:适应内容不断更新的动态网站。
本篇博客是以我的上一篇博客Scrapy爬取多级页面数据-优快云博客进行代码改动实现数据的增量爬取。
我的思路是根据新闻发布的时间来限制爬取,就是每一次爬取后都记录下本次爬取的新闻的最新的时间,我是将最新时间也保存在数据库上的一张表上,下次爬取时根据表中的最新时间来限制爬取页数,当某一页上的新闻发布时间晚于最新时间时则终止爬取,同时更新时间表上的最新时间以此来达到只爬取没有爬取过的新闻信息。
tehran_times.py
import pymysql
import scrapy
from crawel_tehran_times.spiders.utils import time_deal
from crawel_tehran_times.items import CrawelTehranTimesItem
from scrapy import signals
from scrapy.signalmanager import dispatcher
from datetime import datetime
from crawel_tehran_times.spiders.utils import latest_time_query
# 定义一个Scrapy爬虫
class TehranTimesSpider(scrapy.Spider):
name = "tehran_times" # 爬虫的名称
allowed_domains = ["tehrantimes.com"] # 允许爬取的域名
default_url = 'https://www.tehrantimes.com' # 默认的URL
base_url = 'https://www.tehrantimes.com/page/archive.xhtml?mn=2&wide=0&dy=2&ms=0&pi={}&yr=2024&tp=698' # 基础URL,用于构造具体的页面URL
start_urls = ['https://www.tehrantimes.com/archive'] # 开始爬取的URL列表
nextPage = None # 下一页的URL
last_scraped_time = latest_time_query() # 最后一次爬取的时间
found_old_news = False # 是否找到了旧的新闻
# 解析函数,用于解析下载的页面
def parse(self, response):
# 从页面中提取新闻的URL
news_urls = response.xpath('//main//div/section/div/section/div/ul/li[@class!="clearfix newspaper" and @class!="clearfix video"]/h3/a/@href').extract()
for news_url in news_urls:
if 'pdf' and 'photo' in news_url:
continue
item = CrawelTehranTimesItem()
item['news_url'] = self.default_url + news_url
# 对每个新闻的URL发起请求,解析新闻详情
yield scrapy.Request(item['news_url'], meta={'item': item}, callback=self.detail_parse)
# 提取下一页的URL,如果存在的话
self.nextPage = response.xpath("//main//div/section/div/section/div/ul/li/a[contains(text(),'Next')]/@href").extract_first()
print(self.nextPage)
if not self.found_old_news and self.nextPage is not None:
# 如果还没有找到旧的新闻,并且存在下一页,那么继续爬取下一页
yield scrapy.Request(self.default_url + self.nextPage, callback=self.parse)
else:
# 否则,结束爬虫
return None
# 解析新闻详情的函数
def detail_parse(self, response):
# 接收上级已爬取的数据
item = response.meta['item']
# 提取新闻的发布时间
release_time = response.xpath("//main/div//div/section/article/div/div[@class='item-date half-left']/text()").extract()[0]
# 转换发布时间的格式
released_datetime = time_deal(release_time)
# 如果发布时间早于最后一次爬取的时间,那么标记为找到了旧的新闻
if self.last_scraped_time is not None and released_datetime < self.last_scraped_time:
self.found_old_news = True
else:
# 否则,提取新闻的其他信息,并保存到item中
# ...
return item
pipelines.py
# 导入所需的库
import logging
from scrapy.utils.project import get_project_settings
import pymysql
from crawel_tehran_times.spiders.utils import time_deal
# 定义一个Scrapy的Item Pipeline
class MysqlPipeline:
def __init__(self):
# 初始化最早的新闻发布时间
self.latest_time = time_deal('January 31, 1500 - 11:6')
# 创建数据库表
def create_table(self):
# SQL语句,创建一个名为tehran_times2的表
sql = """
CREATE TABLE IF NOT EXISTS `tehran_times2` (
`news_id` INT AUTO_INCREMENT PRIMARY KEY,
`news_url` varchar(255) DEFAULT NULL,
`news_title` varchar(255) DEFAULT NULL,
`release_time` datetime DEFAULT NULL,
`update_time` datetime DEFAULT NULL,
`news_content` text,
`img_url` varchar(255) DEFAULT NULL,
`news_tags` varchar(255) DEFAULT NULL,
`news_type` varchar(255) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
"""
self.cursor.execute(sql)
# 当爬虫打开时,连接到数据库
def open_spider(self, spider):
# 从项目设置中获取数据库配置信息
settings = get_project_settings()
self.host = settings['DB_HOST']
self.port = settings['DB_PORT']
self.user = settings['DB_USER']
self.password = settings['DB_PASSWROD']
self.name = settings['DB_NAME']
self.charset = settings['DB_CHARSET']
# 连接到数据库
self.connect()
# 创建数据表
self.create_table()
# 连接到数据库的方法
def connect(self):
self.conn = pymysql.connect(
host=self.host,
port=self.port,
user=self.user,
password=self.password,
db=self.name,
charset=self.charset
)
self.cursor = self.conn.cursor()
# 处理每个Item的方法
def process_item(self, item, spider):
# 如果新闻的发布时间比最早的新闻发布时间晚,更新最早的新闻发布时间
if self.latest_time < item['release_time']:
self.latest_time = item['release_time']
# 构造插入数据的SQL语句
sql = 'insert into tehran_times2(news_url,news_title,release_time,update_time,news_content,img_url,news_tags,news_type) values("{}","{}","{}","{}","{}","{}","{}","{}")'.format(
item['news_url'], item['news_title'], item['release_time'], item['update_time'], item['news_content'], item['img_url'], item['news_tags'], item['news_type'])
# 执行SQL语句,插入数据
try:
self.cursor.execute(sql)
self.conn.commit()
logging.info('插入数据成功')
except Exception as e:
self.conn.rollback()
logging.info('插入失败进行了回滚',e)
return item
# 当爬虫关闭时,关闭数据库连接
def close_spider(self, spider):
# 创建一个名为latest_time的表,用于存储每个网站最新的新闻发布时间
table_create_sql = """
CREATE TABLE IF NOT EXISTS `latest_time` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`website_name` varchar(255) DEFAULT NULL,
`news_latest_time` datetime DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
"""
self.cursor.execute(table_create_sql)
self.conn.commit()
# 检查latest_time表中是否已经有这个网站的记录
data_if_exist_sql = "select * from latest_time where website_name='tehran_times'"
self.cursor.execute(data_if_exist_sql)
result = self.cursor.fetchone()
# 如果有记录,更新记录;如果没有记录,插入新记录
if result:
update_sql = 'update latest_time set news_latest_time="{}" where website_name="tehran_times"'.format(self.latest_time)
self.cursor.execute(update_sql)
self.conn.commit()
else:
insert_sql = 'insert into latest_time(website_name,news_latest_time) values("tehran_times","{}")'.format(self.latest_time)
self.cursor.execute(insert_sql)
self.conn.commit()
# 关闭数据库连接
self.cursor.close()
self.conn.close()
我还在 spiders下新建了一个utils.py文件里面是时间数据处理函数以及新闻最新时间获取函数
from datetime import datetime
import pymysql
def time_deal(old_time):
# 解析原始字符串为datetime对象
# 注意:这里假设时间是上午22:03,如果是下午,需要调整时间
date_time_obj = datetime.strptime(old_time, "%B %d, %Y - %H:%M")
# 格式化datetime对象为所需的字符串格式
# formatted_string = date_time_obj.strftime("%Y-%m-%d %H:%M")
# return formatted_string
return date_time_obj
def latest_time_query():
host = 'localhost'
user = 'root'
password = '123456'
database = 'public_opinion'
# 创建连接
connection = pymysql.connect(host=host, user=user, password=password, db=database)
# 创建游标对象
cursor = connection.cursor()
table_if_exists = 'show tables like "latest_time"'
cursor.execute(table_if_exists)
result = cursor.fetchone()
if result:
data_if_exist_sql = "select * from latest_time where website_name='tehran_times'"
cursor.execute(data_if_exist_sql)
result = cursor.fetchone()
if result:
latest_time = str(result[2])
# 假设返回的日期时间字符串是这样的:
# date_string = "2024-02-06 09:31:27"
# 你需要知道日期时间字符串的格式
date_format = "%Y-%m-%d %H:%M:%S"
# 使用 strptime 将字符串转换成 datetime 对象
datetime_object = datetime.strptime(latest_time, date_format)
# print(datetime_object) # 输出 datetime.datetime 类型的对象
return datetime_object
else:
return None
else:
return None