从数据库渲染含Vue组件的HTML:Tweet组件无法识别问题
问题描述
我用Spring做后端、Jsoup爬取新闻文章,已获取文章内容,但需要提取并渲染其中的推文。
当前尝试的方案行不通:后端爬取文章后将HTML存入数据库,前端通过ID获取文章后,想用vue-tweet-embed组件渲染推文。流程逻辑没问题,但v-html无法识别HTML里的
相关代码
Java 后端代码
Elements aTags = paragraph.select("a[href*=twitter.com]"); for (Element aTag : aTags) { String tweetUrl = aTag.attr("href").split("\\?")[0]; content.append("<Tweet tweet-url='" + tweetUrl + "'></Tweet>" + System.lineSeparator() + System.lineSeparator()); }
Vue 前端代码
<template> <div class="container"> <div v-for="content in article" v-bind:key="content.id" > <h1 style="font-size: 50px; text-align: left; font-weight: bolder;">{{ content.title }}</h1> <img class="article-image" :src="content.urlToImage"> <h4>read from the original source here:</h4> <a class="a-article" v-bind:href="content.url" target="_blank"> {{ content.url }}</a> <Tweet></Tweet> <div class="content" v-html="content.text"></div> <CommentSection :articleId="content.article_id"></CommentSection> </div> </div> </template> <script> export default { data() { return { article: [], } }, created() { ArticleService.getArticleContent(this.$route.params.id).then(response => { this.article = response.data }) } } </script>
待渲染的HTML片段(来自数据库的article.text)
<p>The Celtics thought it was funny — Jaylen Brown jokingly offered Blake Griffin his mask after the Bucks’ Thanasis Antetokounmpo headbutted the Celtic forward.</p> <p>The NBA didn’t find it as amusing and suspended Antetokounmpo for one game for the incident.</p> <p>Thanasis ejected after head-butting Blake Griffin 😳 pic.twitter.com/gaQSIiFX59</p> <p>— Bleacher Report (@BleacherReport) March 31, 2023</p> <tweet tweet-url="https://twitter.com/BleacherReport/status/1641617577003880448"></tweet> <p></p> <p>Griffin was given a Flagrant 1 foul at the time, but mostly the Celtics had a good laugh.</p> <p>Jaylen really gave Blake his mask 🤣🤣🤣@FCHWPO @blakegriffin23 pic.twitter.com/Gwn9AckWP3</p> <tweet tweet-url="https://twitter.com/FCHWPO"></tweet> <tweet tweet-url="https://twitter.com/blakegriffin23"></tweet> <p>— Celtics on NBC Sports Boston (@NBCSCeltics) March 31, 2023</p> <tweet tweet-url="https://twitter.com/NBCSCeltics/status/1641617185511464961"></tweet>
解决方案
方案1:重构数据存储,分离文本与推文链接(推荐)
后端不要将
- 爬取时,提取文章纯文本内容,同时单独收集所有推文URL,存入数据库的独立字段(比如
tweetUrls数组) - 前端获取文章后,用
v-html渲染纯文本,再循环tweetUrls数组渲染组件
后端修改示例
StringBuilder articleText = new StringBuilder(); List<String> tweetUrls = new ArrayList<>(); Elements aTags = paragraph.select("a[href*=twitter.com]"); for (Element aTag : aTags) { String tweetUrl = aTag.attr("href").split("\\?")[0]; tweetUrls.add(tweetUrl); // 保留原链接的HTML,避免丢失原文结构 articleText.append(aTag.outerHtml()).append(System.lineSeparator()); } // 将articleText和tweetUrls一起存入数据库,返回给前端
前端修改示例
<template> <div class="container"> <div v-for="content in article" :key="content.id" > <!-- 其他内容保持不变 --> <div class="content" v-html="content.text"></div> <!-- 循环渲染推文组件 --> <Tweet v-for="url in content.tweetUrls" :key="url" :tweet-url="url"></Tweet> <CommentSection :articleId="content.article_id"></CommentSection> </div> </div> </template>
方案2:动态编译Vue组件(不推荐,有性能与安全风险)
如果必须保留HTML中的
- 安装对应依赖:Vue3用
@vue/compiler-sfc,Vue2用vue-template-compiler - 在组件中动态编译包含
标签的HTML字符串为可渲染的Vue组件
Vue3 示例
<script setup> import { compile, h } from 'vue' import Tweet from 'vue-tweet-embed' const renderTweetHtml = (html) => { const render = compile(html) return h({ render, components: { Tweet } }) } </script> <template> <div class="container"> <div v-for="content in article" :key="content.id"> <!-- 其他内容 --> <component :is="renderTweetHtml(content.text)"></component> </div> </div> </template>
注意:动态编译会降低页面性能,若HTML来自不可信源还存在XSS风险,仅在完全可控的场景下使用。
更优的推文爬取方案
- 调用Twitter API:后端直接调用官方API(需开发者账号)获取推文的完整结构化数据(内容、作者、时间、媒体等),存入数据库后,前端可自定义推文样式,无需依赖第三方嵌入包
- 提取推文核心信息:若无法使用API,用Jsoup提取推文的文本、图片、作者、发布时间等核心字段,存为结构化数据,前端自己实现推文UI,避免第三方组件的兼容性问题
内容的提问来源于stack exchange,提问作者Philip
相关产品推荐
相关产品推荐

