Files
param-auto-manager/routes/articles.py
T
hz4th_coder 76e24c4fa3 新增搜索标题和网页标题双字段
- 数据库新增 search_title 字段存储搜索结果标题
- product_names 存储抓取到的网页标题
- 搜索页面显示两个标题(搜索标题 + 网页标题)
- 内容库页面也显示两个标题
2026-07-14 00:00:50 +08:00

300 lines
9.0 KiB
Python

"""
文章内容库管理 API
"""
from flask import Blueprint, request, jsonify
from models.database import db
from services.search_service import search_service
bp = Blueprint('articles', __name__, url_prefix='/api/articles')
@bp.route('', methods=['GET'])
def list_articles():
"""获取文章列表"""
limit = request.args.get('limit', 100, type=int)
offset = request.args.get('offset', 0, type=int)
articles = db.get_all_articles(limit=limit, offset=offset)
# 解析JSON字段
for article in articles:
article['product_names'] = __import__('json').loads(article.get('product_names', '[]'))
article['keywords'] = __import__('json').loads(article.get('keywords', '[]'))
return jsonify({
'success': True,
'articles': articles,
'count': len(articles)
})
@bp.route('/search', methods=['GET'])
def search_articles():
"""搜索文章"""
keyword = request.args.get('q', '')
category = request.args.get('category')
if not keyword:
return jsonify({'error': '请提供搜索关键词'}), 400
articles = db.search_articles(keyword, category)
# 解析JSON字段
for article in articles:
article['product_names'] = __import__('json').loads(article.get('product_names', '[]'))
article['keywords'] = __import__('json').loads(article.get('keywords', '[]'))
return jsonify({
'success': True,
'articles': articles,
'count': len(articles)
})
@bp.route('/<int:article_id>', methods=['GET'])
def get_article(article_id):
"""获取文章详情"""
article = db.get_article_by_id(article_id)
if not article:
return jsonify({'error': '文章不存在'}), 404
article['product_names'] = __import__('json').loads(article.get('product_names', '[]'))
article['keywords'] = __import__('json').loads(article.get('keywords', '[]'))
return jsonify({
'success': True,
'article': article
})
@bp.route('', methods=['POST'])
def create_article():
"""创建文章(手动添加)"""
data = request.get_json()
required_fields = ['product_names', 'summary', 'content', 'source']
for field in required_fields:
if field not in data:
return jsonify({'error': f'缺少必填字段: {field}'}), 400
article_id = search_service.save_to_articles(
product_names=data['product_names'],
category=data.get('category'),
keywords=data.get('keywords', []),
summary=data['summary'],
content=data['content'],
source=data['source'],
url=data.get('url')
)
return jsonify({
'success': True,
'article_id': article_id,
'message': '文章创建成功'
})
@bp.route('/<int:article_id>', methods=['DELETE'])
def delete_article(article_id):
"""删除文章"""
success = db.delete_article(article_id)
if success:
return jsonify({
'success': True,
'message': '文章已删除'
})
else:
return jsonify({'error': '文章不存在或删除失败'}), 404
@bp.route('/fetch', methods=['POST'])
def fetch_article():
"""从URL抓取文章"""
data = request.get_json()
url = data.get('url')
search_title = data.get('search_title') # 搜索结果的标题
if not url:
return jsonify({'error': '请提供URL'}), 400
result = search_service.fetch_url_content(url)
if result and result.get('success'):
# 自动保存到内容库
# product_names 使用抓取到的标题,search_title 使用搜索结果的标题
article_id = search_service.save_to_articles(
product_names=data.get('product_names', [result['title']]),
category=data.get('category'),
keywords=data.get('keywords', []),
summary=result.get('description', ''),
content=result['content'],
source=url,
url=url,
search_title=search_title
)
return jsonify({
'success': True,
'article_id': article_id,
'data': result
})
else:
error_msg = result.get('error', '抓取失败') if result else '抓取失败'
return jsonify({'success': False, 'error': error_msg}), 500
@bp.route('/internet-search', methods=['POST'])
def internet_search():
"""互联网搜索(浏览器方式)"""
data = request.get_json()
keyword = data.get('keyword', '')
max_results = data.get('max_results', 10)
engine = data.get('engine', 'bing_cn') # 默认 Bing 中国
use_cache = data.get('use_cache', True) # 默认使用缓存
cache_days = data.get('cache_days', 7) # 默认缓存7天
if not keyword:
return jsonify({'error': '请提供搜索关键词'}), 400
# 检查是否有缓存
cached = None
if use_cache:
cached = db.get_search_cache(keyword, engine)
if cached:
return jsonify({
'success': True,
'keyword': keyword,
'engine': engine,
'results': cached['results'],
'count': cached['count'],
'cached': True,
'cached_at': cached['cached_at'],
'expires_at': cached['expires_at']
})
# 执行互联网搜索
results = search_service.search_internet(keyword, max_results, engine, use_cache, cache_days)
return jsonify({
'success': True,
'keyword': keyword,
'engine': engine,
'results': results,
'count': len(results),
'cached': False
})
@bp.route('/internet-search-and-fetch', methods=['POST'])
def internet_search_and_fetch():
"""互联网搜索并抓取内容"""
data = request.get_json()
keyword = data.get('keyword', '')
max_results = data.get('max_results', 5)
category = data.get('category')
if not keyword:
return jsonify({'error': '请提供搜索关键词'}), 400
# 执行互联网搜索
results = search_service.search_internet(keyword, max_results)
# 抓取每个结果的详细内容
fetched_results = []
for r in results:
url = r.get('url')
if url:
content = search_service.fetch_url_content(url)
if content:
fetched_results.append({
'title': r['title'],
'url': url,
'source': r['source'],
'fetched_content': content
})
return jsonify({
'success': True,
'keyword': keyword,
'results': fetched_results,
'count': len(fetched_results)
})
# ========== 失败URL管理 ==========
@bp.route('/failed-urls', methods=['GET'])
def get_failed_urls():
"""获取失败的URL列表"""
limit = request.args.get('limit', 100, type=int)
urls = db.get_failed_urls(limit=limit)
return jsonify({
'success': True,
'urls': urls,
'count': len(urls)
})
@bp.route('/failed-urls', methods=['POST'])
def add_failed_url():
"""记录失败的URL"""
data = request.get_json()
url = data.get('url')
title = data.get('title')
error_message = data.get('error_message')
source = data.get('source', 'search')
if not url:
return jsonify({'error': '请提供URL'}), 400
url_id = db.add_failed_url(url, title, error_message, source)
return jsonify({
'success': True,
'url_id': url_id,
'message': '失败URL已记录'
})
@bp.route('/failed-urls/count', methods=['GET'])
def get_failed_url_count():
"""获取失败URL数量"""
count = db.get_failed_url_count()
return jsonify({
'success': True,
'count': count
})
@bp.route('/failed-urls/<int:url_id>', methods=['DELETE'])
def delete_failed_url(url_id):
"""删除失败URL记录"""
success = db.delete_failed_url(url_id)
if success:
return jsonify({'success': True, 'message': '记录已删除'})
else:
return jsonify({'error': '记录不存在'}), 404
@bp.route('/failed-urls/clear', methods=['POST'])
def clear_failed_urls():
"""清空所有失败URL记录"""
count = db.clear_failed_urls()
return jsonify({
'success': True,
'message': f'已清空 {count} 条记录'
})
@bp.route('/failed-urls/retry', methods=['POST'])
def retry_failed_url():
"""重试失败的URL"""
data = request.get_json()
url = data.get('url')
if not url:
return jsonify({'error': '请提供URL'}), 400
# 尝试抓取
result = search_service.fetch_url_content(url)
if result and result.get('content'):
# 成功,标记为已处理
db.mark_url_success(url)
return jsonify({
'success': True,
'message': '抓取成功',
'data': result
})
else:
# 仍然失败
db.add_failed_url(url, error_message='重试失败')
return jsonify({'error': '抓取仍然失败'}), 500