scrapy爬虫实战(二): 结合Selenium实现动态加载网页数据采集(详细解说爬取过程以及完整代码)
教你如何使用scrapy+selenium爬取动态加载网页数据
目录
3、selenium模拟用户点击网页并加载更多(middlewares)
一、环境准备
1、python
python3以上,你可以直接下个anaconda,参考之前我写的博客
Anaconda安装+scrapy部署及初步认识-CSDN博客
2、mysql
3、scrapy
参考:Anaconda安装+scrapy部署及初步认识-CSDN博客
4、selenium+chromdriver
因为我的浏览器是chrom,所以用的chromdiver
参考:scrapy爬虫实战:爬取财经网站新闻数据(动态渲染页面)---详细图文解说-CSDN博客
二、实战
1、项目介绍
本项目要爬取的数据是财联社的头条新闻数据,模拟用户点击加载更多按钮,获取完整的网页源码,并把数据存储在mysql数据库。
财联社是主流的财经新闻媒体,专注于中国证券市场动态的分析、报道。
然后按F12分析发现本网页呈现的逻辑:列表数据是通过Ajax加载的。
数据通过 AJAX 加载 是指网页的内容(如列表、表格、动态更新的信息)不是直接写在初始 HTML 代码中,而是通过 JavaScript 异步请求(AJAX) 从服务器获取数据,再动态插入到页面中的技术。
要想爬取全部头条数据得使用selenium模拟人工点击到加载完成才能获取。
2、创建项目
新建项目
首先新建一个Scrapy项目,名字叫做financeSpider,创建命令如下(在base环境中执行):
(看过上个博客已经创建过项目的不需要再创建)
scrapy startproject financeSpider
接下来进入项目,然后新建一个Spider,名称cls_finance,命令如下:
cd ./financeSpider
scrapy genspider cls_finance https://www.cls.cn/
定义Item(items)
定义需要爬取的字段,在items.py里面定义一个FinancespiderItem,代码如下(比上个博客的数据结构多了个news_source字段,记录文章来源的)
class FinancespiderItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
source=scrapy.Field()
source_url= scrapy.Field()
title = scrapy.Field()
link = scrapy.Field()
content = scrapy.Field()
news_source = scrapy.Field()
update_time = scrapy.Field()
mysql建表
建表并给url列设为唯一,ddl命令如下
CREATE TABLE if not exists finance_news (
id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT, -- ✅ 自增ID(无符号整数)
url VARCHAR(255) NOT NULL UNIQUE COMMENT '新闻url', -- ✅ 唯一URL(
title VARCHAR(255) COMMENT '新闻标题',
source VARCHAR(255) COMMENT '新闻来源网站',
source_url VARCHAR(255) COMMENT '来源url',
content TEXT COMMENT '新闻内容',
news_source VARCHAR(255) COMMENT '新闻来源',
update_time VARCHAR(255) COMMENT '新闻更新时间',
dt DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '数据写入时间', -- ✅ 时间戳(自动记录时间)
-- 可选:添加普通索引(根据查询需求)
INDEX idx_dt (dt) -- 如果经常按时间查询可加索引
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
# 加唯一索引
ALTER TABLE finance_news
ADD UNIQUE INDEX idx_unique_url (url);
3、selenium模拟用户点击网页并加载更多(middlewares)
在middlewares文件中,新建selenium下载中间件(middlewares),完整代码如下
class SeleniumMiddleware:
def process_request(self, request, spider):
# 检查是否需要使用Selenium,默认关闭
if not request.meta.get('use_selenium', False):
return None # 继续正常处理
# 配置浏览器参数
chrome_options = Options()
# chrome_options.add_argument("--ignore-certificate-errors") # 忽略 SSL 证书错误
# chrome_options.add_argument('--ignore-ssl-errors')
chrome_options.add_argument('--headless') # 无头模式
driver = webdriver.Chrome(options=chrome_options)
try:
driver.get(request.url)
click_attempts = 0 # 点击尝试计数器
max_clicks = 10 # 最大点击次数(防死循环)
while click_attempts < max_clicks:
try:
# 等待“加载更多”按钮出现
wait = WebDriverWait(driver, 10)
load_more_button = wait.until(EC.presence_of_element_located(
(By.XPATH, "//div[contains(@class, 'list-more-button') and
contains(text(), '加载更多')]")))
# 点击“加载更多”按钮
# 做个有点击按钮的判断,没有就退出循环
if load_more_button:
load_more_button.click()
else:
print ("没有找到加载更多按钮")
break
# 强制等待动态内容加载(根据网络情况调整)
time.sleep(3)
click_attempts += 1
except (NoSuchElementException, TimeoutException):
print("已经加载到最后")
break
# 获取完整渲染后的页面源码
body = driver.page_source
return HtmlResponse(url=driver.current_url, body=body, encoding='utf-8')
finally:
driver.quit()
代码重点解析:
- 模拟点击加载更多按钮,因为要点击多次设置了while循环,以防发生死循环所以做了个最大值限制
- selenium耗内存,下载效率慢,所以在request设置了开启和关闭标签,
- 一定要记得及时关闭浏览器,释放内存
4、管道文件(Pipelines)
mysql存储
把数据写入mysql数据库(同上一篇博客)
import pymysql
from itemadapter import ItemAdapter
from scrapy.exceptions import DropItem
class MySQLPipeline:
def __init__(self, host, port, user, password, db, charset):
self.host = host
self.port = port
self.user = user
self.password = password
self.db = db
self.charset = charset
self.connection = None
self.cursor = None
@classmethod
def from_crawler(cls, crawler):
return cls(
host=crawler.settings.get('MYSQL_HOST'),
port=crawler.settings.get('MYSQL_PORT'),
user=crawler.settings.get('MYSQL_USER'),
password=crawler.settings.get('MYSQL_PASSWORD'),
db=crawler.settings.get('MYSQL_DATABASE'),
charset=crawler.settings.get('MYSQL_CHARSET')
)
def open_spider(self, spider):
"""连接数据库"""
self.connection = pymysql.connect(
host=self.host,
port=self.port,
user=self.user,
password=self.password,
db=self.db,
charset=self.charset,
cursorclass=pymysql.cursors.DictCursor
)
self.cursor = self.connection.cursor()
def close_spider(self, spider):
"""关闭连接"""
self.connection.close()
def process_item(self, item, spider):
"""处理Item"""
try:
# 构建SQL语句(根据你的表结构修改)
sql = """
INSERT INTO finance_news(
url,
title,
source,
source_url,
content,
news_source,
update_time
) VALUES ( %s, %s, %s, %s, %s , %s, %s )
ON DUPLICATE KEY UPDATE
title = VALUES(title),
source = VALUES(source),
source_url = VALUES(source_url),
content = VALUES(content),
news_source = VALUES(news_source),
update_time = VALUES(update_time);"""
params = (item["link"],item["title"],item["source"],item["source_url"],item["content"],item["news_source"],item["update_time"])
# 从item提取数据(字段名需要对应)
self.cursor.execute(sql,params)
self.connection.commit()
except Exception as e:
self.connection.rollback()
raise DropItem(f"Error saving item to MySQL: {str(e)}")
return item
CSV存储
没有mysql的可以用本地文件存储数据
import csv
from itemadapter import ItemAdapter
import datetime
# 获取当前日期
today = datetime.date.today()
class FinanceCsvPipeline(object):
def process_item(self, item, spider):
with open(f"finance_news_{today}.csv", "a+", encoding="utf-8") as f:
w = csv.writer(f)
row=item["link"],item["title"],item["source"],item["source_url"],item["content"],item["news_source"],item["update_time"]
w.writerow(row)
return item
做完pipelines文件修改后,一定要记得修改配置文件(settings)的ITEM_PIPELINES
5、配置文件(settings)
*重点修改
释放DOWNLOADER_MIDDLEWARES
DOWNLOADER_MIDDLEWARES = {
"financeSpider.middlewares.FinancespiderSpiderMiddleware": 543,#原来的
"financeSpider.middlewares.SeleniumMiddleware": 600 #新建的selenium中间件
}
#根据自己存储的具体情况修改
ITEM_PIPELINES = {
# "financeSpider.pipelines.FinanceSpiderPipeline": 300,
#存储在本地文件csv
#"financeSpider.pipelines.FinanceCsvPipeline": 300,
#存储在mysql数据库
"financeSpider.pipelines.MySQLPipeline": 300,
}
数字代码级别,级别越低优先级越高
完整代码如下(其他配置同上一篇博客):
BOT_NAME = "financeSpider"
SPIDER_MODULES = ["financeSpider.spiders"]
NEWSPIDER_MODULE = "financeSpider.spiders"
#修改请求头,可以弄得完整点,这个是全局配置,spider请求的时候不需要加
# Override the default request headers:
DEFAULT_REQUEST_HEADERS = {
'Accept': 'image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8',
'accept-encoding': 'gzip, deflate, br, zstd',
'Accept-Language': 'zh-CN,zh;q=0.9',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
}
#下载中间件
DOWNLOADER_MIDDLEWARES = {
"financeSpider.middlewares.FinancespiderSpiderMiddleware": 543,
"financeSpider.middlewares.SeleniumMiddleware": 600
}
#mysql的配置参数根据自己的mysql地址修改
# mysql SETTING========
MYSQL_HOST='192.168.X.XX'
MYSQL_PORT=3306
MYSQL_USER='root'
MYSQL_PASSWORD='123456'
MYSQL_DATABASE = 'finance'
MYSQL_CHARSET='utf8mb4'
# Configure item pipelines
# See https://docs.scrapy.org/en/latest/topics/item-pipeline.html
ITEM_PIPELINES = {
# "financeSpider.pipelines.FinanceSpiderPipeline": 300,
#存储在csv文件
#"financeSpider.pipelines.FinanceCsvPipeline": 300,
#存储在mysql数据库
"financeSpider.pipelines.MySQLPipeline": 300,
}
#推荐加上的配置参数
ROBOTSTXT_OBEY = False
RETRY_HTTP_CODES = [401, 403, 500, 502, 503, 504]
CONCURRENT_REQUESTS = 10
6、爬虫文件(cls_finance)
import scrapy
import time
from bs4 import BeautifulSoup
from financeSpider.items import FinancespiderItem
# 爬取财联社的头条新闻,模拟人类点击加载更多 https://www.cls.cn/depth?id=1000 使用selenium + Beautifulsoup技术 scrapy crawl cls_finance
class ClsFinanceSpider(scrapy.Spider):
name = "cls_finance"
allowed_domains = ["cls.cn"]
start_urls = ["https://www.cls.cn/"]
#因为要使用selenium重写请求首页
def start_requests(self):
url="https://www.cls.cn/depth?id=1000"
yield scrapy.Request(url=url,meta={"use_selenium":True},callback=self.parse) # 标记需要处理 只有需要selenium的时候标记,默认不用
def parse(self, response):
soup = BeautifulSoup(response.text, 'lxml')
# print (soup)
# 用css模糊匹配
items = soup.select('div[class*="subject-interest-list"]')
# print (items)
for t in items:
item = FinancespiderItem()
item['source'] = '财联社'
item['source_url'] = response.url
item['title'] = t.select_one('div[class*="subject-interest-title"]').text.replace('原创', '').strip()
item['link'] = "https://www.cls.cn" + t.a['href']
# print(item['link'] )
# print (item['title'])
time.sleep(1)
yield scrapy.Request(url=item['link'], meta={"item": item}, callback=self.parse1)
def parse1(self, response):
item = response.meta['item']
soup = BeautifulSoup(response.text, 'lxml')
print("正在抓取内容")
# 声明变量并赋值,这样能在解析异常的时候还能正常入表,以防数据丢失
content = "none"
dt = "none"
source = "none"
# 文本解析异常处理
try:
content = soup.select_one('div[class*="detail-content"]').text.strip()
dt = soup.select_one('div[class="f-l m-r-10"]').text.strip()[0:16]
source = soup.select_one('div[class="f-l"]').text.strip()
except AttributeError as e:
print (f"{e}:文本解析报错")
item["content"] = content
item['update_time']=dt
item['news_source'] =source
#
# print (item["content"] +"\t"+item['update_time'])
yield item
7、运行测试
在base环境,写入命令
scrapy crawl cls_finance
运行结果如下:
最后运行完的结果 :
101条新闻全部爬取成功。
数据结果如下:
我们已经可以用scrapy+selenium成功爬取数据啦!
………………………………………………………………………………………………………………
大家有什么优化或者建议欢迎评论区告诉我,我们一起共同进步,谢谢!

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐
所有评论(0)