scrapy爬虫实战(二): 结合Selenium实现动态加载网页数据采集(详细解说爬取过程以及完整代码)
一、环境准备
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,代码如下(跟上个博客的数据结构一样,可以不用再定义)
class FinancespiderItem(scrapy.Item): source=scrapy.Field() source_url= scrapy.Field() title = scrapy.Field() link = scrapy.Field() content = 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 '文章内容', 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代码重点解析:
- 模拟点击加载更多按钮,因为要点击多次设置了while循环,以防发生死循环所以做了个最大值限制
- selenium耗内存,下载效率慢,所以在request设置了开启和关闭标签,
- 一定要记得及时关闭浏览器,释放内存
4、管道文件(Pipelines)
Pipeline文件,把数据写入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语句(根据你的表结构修改,按key值进行更新数据) sql = """ INSERT INTO finance_news( url, title, source, source_url, content, update_time ) VALUES ( %s, %s, %s, %s, %s , %s ) ON DUPLICATE KEY UPDATE title = VALUES(title), source = VALUES(source), source_url = VALUES(source_url), content = VALUES(content), update_time = VALUES(update_time);""" params = (item["link"],item["title"],item["source"],item["source_url"],item["content"],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
5、配置文件(settings)
释放DOWNLOADER_MIDDLEWARES
DOWNLOADER_MIDDLEWARES = { "financeSpider.middlewares.FinancespiderSpiderMiddleware": 543,#原来的 "financeSpider.middlewares.SeleniumMiddleware": 600 #新建的selenium中间件 }
级别越低优先级越高
完整代码如下(其他配置同上一篇博客):
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, #根据自己的管道文件名称修改 "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技术 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) # meta={"use_selenium":True} 只有需要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 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("正在抓取内容") try: item["content"] = soup.select_one('div[class*="content-left"]').text.strip() item['update_time'] = soup.select_one('div[]').text.strip()[0:16] except AttributeError as e: print ("AttributeError:获取文章文本内容解析报错") # print (item["content"] +"\t"+item['update_time']) yield item
7、运行测试
在base环境,写入命令
scrapy crawl cls_finance
运行结果如下:
最后运行完的结果 :
101条新闻全部爬取成功。
数据结果如下:
我们已经可以用scrapy+selenium成功爬取数据啦!
………………………………………………………………………………………………………………
大家有什么优化或者建议欢迎评论区告诉我,我们一起共同进步,谢谢!
- 一定要记得及时关闭浏览器,释放内存
- selenium耗内存,下载效率慢,所以在request设置了开启和关闭标签,