非你莫属做网站的卖网币起家的,东城做网站,北京好的网站设计机构,汇鑫网站建设方便介绍 yt-dlp
Github 项目#xff1a;https://github.com/yt-dlp/yt-dlp A feature-rich command-line audio/video downloader 一个功能丰富的视频与音频命令行下载器
原因与功能
之前我用的 cobalt 因为它不再提供Client Web功能#xff0c;只能去它的官网使用。 翻 redd…介绍 yt-dlp
Github 项目https://github.com/yt-dlp/yt-dlp A feature-rich command-line audio/video downloader 一个功能丰富的视频与音频命令行下载器
原因与功能
之前我用的 cobalt 因为它不再提供Client Web功能只能去它的官网使用。 翻 reddit 找到这个 YT-DLP但它是个命令行工具考虑参数大多很少用到给它加个web 壳子又可以放到docker里面运行。
在网页填入url只列出含有视频音频的文件。点下载后文件可以保存在本地。命令的运行输出也在页面上显示。占用端口: 9012
YT-DLP 程序
代码在 Claude AI 帮助下完成前端全靠它Nice~
界面 目录结构
20.YT-DLP/
├── Dockerfile
├── app.py
├── static/
│ ├── css/
│ │ └── style.css
│ └── js/
│ └── script.js
├── templates/
│ └── index.html
└── temp_downloads/
完整代码
1. app.py
# app.py
from flask import Flask, render_template, request, jsonify, send_file
import yt_dlp
import os
import shutil
from werkzeug.utils import secure_filename
import time
import logging
import queue
from datetime import datetime
import sysapp Flask(__name__)# 创建固定的临时目录
TEMP_DIR os.path.join(os.path.dirname(os.path.abspath(__file__)), temp_downloads)
if not os.path.exists(TEMP_DIR):os.makedirs(TEMP_DIR)# 存储下载信息的字典
DOWNLOADS {}# 创建日志队列
log_queue queue.Queue(maxsize1000)class QueueHandler(logging.Handler):def __init__(self, log_queue):super().__init__()self.log_queue log_queuedef emit(self, record):try:# 过滤掉 Werkzeug 的常规访问日志if record.name werkzeug and any(x in record.getMessage() for x in [127.0.0.1,GET /api/logs,GET /static/,GET / HTTP/1.1]):return# 清理消息格式msg self.format(record)if record.name app:# 移除 INFO:app: 等前缀msg msg.split( - )[-1]log_entry {timestamp: datetime.fromtimestamp(record.created).isoformat(),message: msg,level: record.levelname.lower(),logger: record.name}# 如果队列满了移除最旧的日志if self.log_queue.full():try:self.log_queue.get_nowait()except queue.Empty:passself.log_queue.put(log_entry)except Exception as e:print(fError in QueueHandler: {e})# 配置日志格式
log_formatter logging.Formatter(%(message)s)# 配置队列处理器
queue_handler QueueHandler(log_queue)
queue_handler.setFormatter(log_formatter)# 配置控制台处理器
console_handler logging.StreamHandler(sys.stdout)
console_handler.setFormatter(log_formatter)# 配置 Flask 日志
app.logger.handlers []
app.logger.addHandler(queue_handler)
app.logger.addHandler(console_handler)
app.logger.setLevel(logging.INFO)# Werkzeug 日志只输出错误
werkzeug_logger logging.getLogger(werkzeug)
werkzeug_logger.handlers []
werkzeug_logger.addHandler(console_handler)
werkzeug_logger.setLevel(logging.WARNING)def cleanup_old_files():清理超过10分钟的临时文件current_time time.time()for token, info in list(DOWNLOADS.items()):if current_time - info[timestamp] 600: # 10分钟try:file_path info[file_path]if os.path.exists(file_path):os.remove(file_path)del DOWNLOADS[token]except Exception as e:app.logger.error(f清理文件失败: {str(e)})def get_video_info(url):获取视频信息包括可用的格式ydl_opts {quiet: True,no_warnings: True,format: None,youtube_include_dash_manifest: True,format_sort: [res:2160, # 4Kres:1440, # 2Kres:1080, # 1080pres:720, # 720pres:480, # 480pfps:60, # 优先60fpsfps, # 然后是其他fpsvcodec:h264, # 优先H.264编码vcodec:vp9, # 然后是VP9acodec # 最后是音频编码]}with yt_dlp.YoutubeDL(ydl_opts) as ydl:try:info ydl.extract_info(url, downloadFalse)formats []def safe_number(value, default0):try:return float(value or default)except (TypeError, ValueError):return default# 处理视频格式for f in info.get(formats, []):vcodec f.get(vcodec, none)acodec f.get(acodec, none)has_video vcodec ! nonehas_audio acodec ! noneheight safe_number(f.get(height, 0))width safe_number(f.get(width, 0))fps safe_number(f.get(fps, 0))tbr safe_number(f.get(tbr, 0))if has_video: # 只处理包含视频的格式format_notes []# 添加分辨率标签if height 2160:format_notes.append(4K)elif height 1440:format_notes.append(2K)# 详细的分辨率信息if height and width:format_notes.append(f{width:.0f}x{height:.0f}p)# FPS信息if fps 0:format_notes.append(f{fps:.0f}fps)# 编码信息if vcodec ! none:codec_name {avc1: H.264,vp9: VP9,av01: AV1}.get(vcodec.split(.)[0], vcodec)format_notes.append(fVideo: {codec_name})# 比特率信息if tbr 0:format_notes.append(f{tbr:.0f}kbps)# 音频信息if has_audio and acodec ! none:format_notes.append(fAudio: {acodec})format_data {format_id: f.get(format_id, ),ext: f.get(ext, ),filesize: f.get(filesize, 0),format_note: - .join(format_notes),vcodec: vcodec,acodec: acodec,height: height,width: width,fps: fps,resolution_sort: height * 1000 fps}if format_data[format_id]:formats.append(format_data)# 按分辨率和FPS排序formats.sort(keylambda x: x[resolution_sort], reverseTrue)# 移除重复的格式seen_resolutions set()unique_formats []for fmt in formats:res_key f{fmt[height]:.0f}p-{fmt[fps]:.0f}fpsif res_key not in seen_resolutions:seen_resolutions.add(res_key)unique_formats.append(fmt)return {title: info.get(title, Unknown),duration: info.get(duration, 0),thumbnail: info.get(thumbnail, ),formats: unique_formats,description: info.get(description, ),channel: info.get(channel, Unknown),view_count: info.get(view_count, 0),}except Exception as e:app.logger.error(f获取视频信息失败: {str(e)})return {error: str(e)}def log_progress(d):if d[status] downloading:try:percent d.get(_percent_str, N/A).strip()speed d.get(_speed_str, N/A).strip()eta d.get(_eta_str, N/A).strip()# 每5%记录一次进度if percent ! N/A and float(percent.rstrip(%)) % 5 1:app.logger.info(f下载进度: {percent} | 速度: {speed} | 剩余时间: {eta})except Exception:passelif d[status] finished:app.logger.info(下载完成开始处理文件...)app.route(/)
def index():渲染主页return render_template(index.html)app.route(/api/info, methods[POST])
def get_info():获取视频信息的API端点url request.json.get(url)if not url:return jsonify({error: URL is required}), 400info get_video_info(url)return jsonify(info)app.route(/api/download, methods[POST])
def download_video():下载视频的API端点url request.json.get(url)format_id request.json.get(format_id)if not url or not format_id:app.logger.error(缺少URL或格式ID)return jsonify({error: URL and format_id are required}), 400try:cleanup_old_files()temp_file os.path.join(TEMP_DIR, fdownload_{time.time_ns()})app.logger.info(f创建临时文件: {os.path.basename(temp_file)})ydl_opts {format: f{format_id}bestaudio/best,outtmpl: temp_file .%(ext)s,quiet: True,merge_output_format: mp4,postprocessors: [{key: FFmpegVideoConvertor,preferedformat: mp4,}],prefer_ffmpeg: True,keepvideo: False,progress_hooks: [log_progress],}app.logger.info(开始下载视频...)with yt_dlp.YoutubeDL(ydl_opts) as ydl:info ydl.extract_info(url, downloadTrue)final_file ydl.prepare_filename(info)filename secure_filename(info[title] .mp4)filesize os.path.getsize(final_file)filesize_mb filesize / (1024 * 1024)app.logger.info(f下载完成: {filename} ({filesize_mb:.1f}MB))download_token os.urandom(16).hex()DOWNLOADS[download_token] {file_path: final_file,filename: filename,timestamp: time.time()}return jsonify({status: success,download_token: download_token,filename: filename})except Exception as e:app.logger.error(f下载失败: {str(e)})return jsonify({error: str(e)}), 500app.route(/api/get_file/token)
def get_file(token):获取下载文件的API端点if token not in DOWNLOADS:app.logger.error(无效的下载令牌)return Invalid or expired download token, 400download_info DOWNLOADS[token]file_path download_info[file_path]filename download_info[filename]if not os.path.exists(file_path):app.logger.error(f文件未找到: {filename})return File not found, 404try:filesize os.path.getsize(file_path)filesize_mb filesize / (1024 * 1024)app.logger.info(f开始发送: {filename} ({filesize_mb:.1f}MB))return send_file(file_path,as_attachmentTrue,download_namefilename,mimetypevideo/mp4)except Exception as e:app.logger.error(f发送文件失败: {str(e)})return str(e), 500finally:def cleanup():try:if token in DOWNLOADS:os.remove(file_path)del DOWNLOADS[token]app.logger.info(f临时文件已清理: {filename})except Exception as e:app.logger.error(f清理文件失败: {str(e)})import threadingthreading.Timer(60, cleanup).start()app.route(/api/logs)
def get_logs():获取日志的API端点logs []temp_queue queue.Queue()try:while not log_queue.empty():log log_queue.get_nowait()logs.append(log)temp_queue.put(log)while not temp_queue.empty():log_queue.put(temp_queue.get_nowait())return jsonify(sorted(logs, keylambda x: x[timestamp], reverseTrue))except Exception as e:app.logger.error(f获取日志失败: {str(e)})return jsonify([])if __name__ __main__:# 确保临时目录存在os.makedirs(TEMP_DIR, exist_okTrue)# 启动时清理旧文件cleanup_old_files()# 运行应用app.run(host0.0.0.0, port9012, debugTrue)
2. index.html
!-- templates/index.html --
!DOCTYPE html
html langen
headmeta charsetUTF-8meta nameviewport contentwidthdevice-width, initial-scale1.0titleYouTube Video Downloader/titlelink relstylesheet href{{ url_for(static, filenamecss/style.css) }}
/head
bodydiv classcontainerh1YouTube Video Downloader/h1div classinput-groupinput typetext idurl-input placeholderEnter YouTube URLbutton idfetch-infoGet Video Info/button/divdiv idvideo-info classhiddendiv classinfo-containerimg idthumbnail src altVideo thumbnaildiv classvideo-detailsh2 idvideo-title/h2p idvideo-duration/p/div/divdiv classformats-containerh3Available Formats/h3div idformat-list/div/div/divdiv idstatus classhidden/div!-- 日志显示区域 --div classlog-containerdiv classlog-headerh3Operation Logs/h3button idclear-logs titleClear logsClear/buttonlabel classauto-scrollinput typecheckbox idauto-scroll checkedAuto-scroll/label/divdiv idlog-display/div/div/divscript src{{ url_for(static, filenamejs/script.js) }}/script
/body
/html
3. style.css
有了 AI 后, style 产生得太简单
/* static/css/style.css */
body {font-family: Arial, sans-serif;margin: 0;padding: 20px;background-color: #f5f5f5;
}.container {max-width: 800px;margin: 0 auto;background-color: white;padding: 20px;border-radius: 8px;box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}h1 {text-align: center;color: #333;margin-bottom: 20px;
}.input-group {display: flex;gap: 10px;margin-bottom: 20px;
}input[typetext] {flex: 1;padding: 10px;border: 1px solid #ddd;border-radius: 4px;font-size: 16px;
}button {padding: 10px 20px;background-color: #007bff;color: white;border: none;border-radius: 4px;cursor: pointer;font-size: 16px;
}button:hover {background-color: #0056b3;
}.hidden {display: none;
}.info-container {display: flex;gap: 20px;margin-bottom: 20px;padding: 15px;background-color: #f8f9fa;border-radius: 4px;
}#thumbnail {max-width: 200px;border-radius: 4px;
}.video-details {flex: 1;
}.video-details h2 {margin: 0 0 10px 0;color: #333;
}.formats-container {border-top: 1px solid #ddd;padding-top: 20px;
}#format-list {display: grid;gap: 10px;
}.format-item {padding: 10px;background-color: #f8f9fa;border-radius: 4px;display: flex;justify-content: space-between;align-items: center;
}#status {margin: 20px 0;padding: 10px;border-radius: 4px;text-align: center;
}#status.success {background-color: #d4edda;color: #155724;
}#status.error {background-color: #f8d7da;color: #721c24;
}/* 日志容器样式 */
.log-container {margin-top: 20px;border: 1px solid #ddd;border-radius: 4px;background-color: #1e1e1e;
}.log-header {padding: 10px;background-color: #2d2d2d;border-bottom: 1px solid #444;display: flex;align-items: center;gap: 10px;
}.log-header h3 {margin: 0;flex-grow: 1;color: #fff;
}.auto-scroll {display: flex;align-items: center;gap: 5px;font-size: 14px;color: #fff;
}#clear-logs {padding: 5px 10px;background-color: #6c757d;color: white;border: none;border-radius: 4px;cursor: pointer;
}#clear-logs:hover {background-color: #5a6268;
}#log-display {height: 300px;overflow-y: auto;padding: 10px;font-family: Consolas, Monaco, monospace;font-size: 13px;line-height: 1.4;background-color: #1e1e1e;color: #d4d4d4;
}.log-entry {margin: 2px 0;padding: 2px 5px;border-radius: 2px;white-space: pre-wrap;word-wrap: break-word;
}.log-timestamp {color: #888;margin-right: 8px;font-size: 0.9em;
}.log-info {color: #89d4ff;
}.log-error {color: #ff8989;
}.log-warning {color: #ffd700;
}/* 滚动条样式 */
#log-display::-webkit-scrollbar {width: 8px;
}#log-display::-webkit-scrollbar-track {background: #2d2d2d;
}#log-display::-webkit-scrollbar-thumb {background: #888;border-radius: 4px;
}#log-display::-webkit-scrollbar-thumb:hover {background: #555;
}
4. script.js
// static/js/script.js
document.addEventListener(DOMContentLoaded, function() {const urlInput document.getElementById(url-input);const fetchButton document.getElementById(fetch-info);const videoInfo document.getElementById(video-info);const thumbnail document.getElementById(thumbnail);const videoTitle document.getElementById(video-title);const videoDuration document.getElementById(video-duration);const formatList document.getElementById(format-list);const status document.getElementById(status);// 日志系统class Logger {constructor() {this.logDisplay document.getElementById(log-display);this.autoScrollCheckbox document.getElementById(auto-scroll);this.clearLogsButton document.getElementById(clear-logs);this.lastLogTimestamp null;this.setupEventListeners();}setupEventListeners() {this.clearLogsButton.addEventListener(click, () this.clearLogs());this.startLogPolling();}formatTimestamp(isoString) {const date new Date(isoString);return date.toLocaleTimeString(en-US, { hour12: false,hour: 2-digit,minute: 2-digit,second: 2-digit,fractionalSecondDigits: 3});}addLogEntry(entry) {const logEntry document.createElement(div);logEntry.classList.add(log-entry);if (entry.level error) {logEntry.classList.add(log-error);} else if (entry.level warning) {logEntry.classList.add(log-warning);} else {logEntry.classList.add(log-info);}const timestamp document.createElement(span);timestamp.classList.add(log-timestamp);timestamp.textContent this.formatTimestamp(entry.timestamp);const message document.createElement(span);message.classList.add(log-message);message.textContent entry.message;logEntry.appendChild(timestamp);logEntry.appendChild(message);this.logDisplay.appendChild(logEntry);if (this.autoScrollCheckbox.checked) {this.scrollToBottom();}}clearLogs() {this.logDisplay.innerHTML ;this.lastLogTimestamp null;}scrollToBottom() {this.logDisplay.scrollTop this.logDisplay.scrollHeight;}async fetchLogs() {try {const response await fetch(/api/logs);const logs await response.json();const newLogs this.lastLogTimestamp ? logs.filter(log log.timestamp this.lastLogTimestamp): logs;if (newLogs.length 0) {newLogs.forEach(log this.addLogEntry(log));this.lastLogTimestamp logs[0].timestamp;}} catch (error) {console.error(Failed to fetch logs:, error);}}startLogPolling() {setInterval(() this.fetchLogs(), 500);}}// 初始化日志系统const logger new Logger();function formatDuration(seconds) {const hours Math.floor(seconds / 3600);const minutes Math.floor((seconds % 3600) / 60);const remainingSeconds seconds % 60;if (hours 0) {return ${hours}:${minutes.toString().padStart(2, 0)}:${remainingSeconds.toString().padStart(2, 0)};}return ${minutes}:${remainingSeconds.toString().padStart(2, 0)};}function formatFileSize(bytes) {if (!bytes) return Unknown size;const sizes [Bytes, KB, MB, GB];const i Math.floor(Math.log(bytes) / Math.log(1024));return ${(bytes / Math.pow(1024, i)).toFixed(2)} ${sizes[i]};}function showStatus(message, isError false) {status.textContent message;status.className isError ? error : success;status.classList.remove(hidden);}async function downloadVideo(url, formatId) {try {logger.addLogEntry({timestamp: new Date().toISOString(),level: info,message: Starting download preparation for format: ${formatId}});showStatus(Preparing download...);const response await fetch(/api/download, {method: POST,headers: {Content-Type: application/json,},body: JSON.stringify({ url, format_id: formatId })});const data await response.json();if (response.ok data.download_token) {logger.addLogEntry({timestamp: new Date().toISOString(),level: success,message: Download token received: ${data.download_token}});showStatus(Starting download...);const iframe document.createElement(iframe);iframe.style.display none;iframe.src /api/get_file/${data.download_token};iframe.onload () {logger.addLogEntry({timestamp: new Date().toISOString(),level: success,message: Download started for: ${data.filename}});showStatus(Download started! Check your browser downloads.);setTimeout(() document.body.removeChild(iframe), 5000);};iframe.onerror () {logger.addLogEntry({timestamp: new Date().toISOString(),level: error,message: Download failed to start});showStatus(Download failed. Please try again., true);document.body.removeChild(iframe);};document.body.appendChild(iframe);} else {const errorMessage data.error || Download failed;logger.addLogEntry({timestamp: new Date().toISOString(),level: error,message: Download failed: ${errorMessage}});showStatus(errorMessage, true);}} catch (error) {logger.addLogEntry({timestamp: new Date().toISOString(),level: error,message: Network error: ${error.message}});showStatus(Network error occurred, true);console.error(error);}}fetchButton.addEventListener(click, async () {const url urlInput.value.trim();if (!url) {showStatus(Please enter a valid URL, true);return;}showStatus(Fetching video information...);try {const response await fetch(/api/info, {method: POST,headers: {Content-Type: application/json,},body: JSON.stringify({ url })});const data await response.json();if (response.ok) {thumbnail.src data.thumbnail;videoTitle.textContent data.title;videoDuration.textContent formatDuration(data.duration);formatList.innerHTML data.formats.filter(format format.format_id format.ext).map(format div classformat-itemspan${format.format_note} (${format.ext}) - ${formatFileSize(format.filesize)}/spanbutton onclickdownloadVideo(${url}, ${format.format_id})Download/button/div).join();videoInfo.classList.remove(hidden);status.classList.add(hidden);logger.addLogEntry({timestamp: new Date().toISOString(),level: info,message: Video information retrieved: ${data.title}});} else {showStatus(data.error || Failed to fetch video info, true);logger.addLogEntry({timestamp: new Date().toISOString(),level: error,message: Failed to fetch video info: ${data.error || Unknown error}});}} catch (error) {showStatus(Network error occurred, true);logger.addLogEntry({timestamp: new Date().toISOString(),level: error,message: Network error: ${error.message}});}});window.downloadVideo downloadVideo;// 支持回车键触发获取视频信息urlInput.addEventListener(keypress, (e) {if (e.key Enter) {fetchButton.click();}});});
以上文件放到相应目录库文件参考 requirements.txt 即可。
Docker 部署
1. Dockerfile
FROM python:3.11-slimWORKDIR /appRUN apt-get update \apt-get install -y --no-install-recommends \ffmpeg \ rm -rf /var/lib/apt/lists/*COPY app.py ./
COPY static/css/style.css ./static/css/
COPY static/js/script.js ./static/js/
COPY templates/index.html ./templates/RUN pip install --no-cache-dir \flask \yt-dlp \werkzeugRUN mkdir -p /app/temp_downloads \chmod 777 /app/temp_downloadsENV FLASK_APPapp.py
ENV PYTHONUNBUFFERED1
ENV FLASK_RUN_HOST0.0.0.0
ENV FLASK_RUN_PORT9012EXPOSE 9012CMD [python, -c, from app import app; app.run(host0.0.0.0, port9012)]
2. requirements.txt
flask
Werkzeug3.0.1
yt-dlp2024.3.10
gunicorn21.2.0
如果你使用这个 .txt 可以去掉版本号。我指定版本号是因我 NAS 的 wheel files 存有多个版本
3. 创建 Image 与 Container
# docker build -t yt-dlp .
# docker run -d -p 9012:9012 --name yt-dlp_container yt-dlp我使用了与 Github 上面项目的相同名字只是为了方便字少。 注在 docker 命令中没有 加入 --restart always, 要编辑一下容器自己添加。
总结
yt-dlp 是一个功能超强的工具可以用 cookie file获取身份认证来下载视频或通过 Mozila 浏览器直接获得 cookie 内容只是说明上这么说我没试过。 Douyin 有 bug 不能下载 , 其它网站没有试。
我有订阅 youtube 这个工具只是娱乐或下载 民国 及以前的其版权已经放弃的影像内容。
请尊重版权 文章转载自: http://www.morning.smrty.cn.gov.cn.smrty.cn http://www.morning.ztrht.cn.gov.cn.ztrht.cn http://www.morning.lbfgq.cn.gov.cn.lbfgq.cn http://www.morning.mxlwl.cn.gov.cn.mxlwl.cn http://www.morning.qkpzq.cn.gov.cn.qkpzq.cn http://www.morning.rgkd.cn.gov.cn.rgkd.cn http://www.morning.lbgsh.cn.gov.cn.lbgsh.cn http://www.morning.jngdh.cn.gov.cn.jngdh.cn http://www.morning.kphyl.cn.gov.cn.kphyl.cn http://www.morning.llthz.cn.gov.cn.llthz.cn http://www.morning.nlgmr.cn.gov.cn.nlgmr.cn http://www.morning.yswxq.cn.gov.cn.yswxq.cn http://www.morning.rkjz.cn.gov.cn.rkjz.cn http://www.morning.zqfjn.cn.gov.cn.zqfjn.cn http://www.morning.ydwnc.cn.gov.cn.ydwnc.cn http://www.morning.xtrnx.cn.gov.cn.xtrnx.cn http://www.morning.okiner.com.gov.cn.okiner.com http://www.morning.rdmz.cn.gov.cn.rdmz.cn http://www.morning.dxhnm.cn.gov.cn.dxhnm.cn http://www.morning.zmqb.cn.gov.cn.zmqb.cn http://www.morning.vjdofuj.cn.gov.cn.vjdofuj.cn http://www.morning.fsfz.cn.gov.cn.fsfz.cn http://www.morning.kjtdy.cn.gov.cn.kjtdy.cn http://www.morning.dwfzm.cn.gov.cn.dwfzm.cn http://www.morning.xmhpq.cn.gov.cn.xmhpq.cn http://www.morning.yngtl.cn.gov.cn.yngtl.cn http://www.morning.cpljq.cn.gov.cn.cpljq.cn http://www.morning.rmmz.cn.gov.cn.rmmz.cn http://www.morning.skwwj.cn.gov.cn.skwwj.cn http://www.morning.mxnhq.cn.gov.cn.mxnhq.cn http://www.morning.rswfj.cn.gov.cn.rswfj.cn http://www.morning.kwfnt.cn.gov.cn.kwfnt.cn http://www.morning.nydgg.cn.gov.cn.nydgg.cn http://www.morning.rzpkt.cn.gov.cn.rzpkt.cn http://www.morning.smggx.cn.gov.cn.smggx.cn http://www.morning.fkrzx.cn.gov.cn.fkrzx.cn http://www.morning.qlxgc.cn.gov.cn.qlxgc.cn http://www.morning.mbmh.cn.gov.cn.mbmh.cn http://www.morning.cyhlq.cn.gov.cn.cyhlq.cn http://www.morning.wmfr.cn.gov.cn.wmfr.cn http://www.morning.rxzcl.cn.gov.cn.rxzcl.cn http://www.morning.mdpkf.cn.gov.cn.mdpkf.cn http://www.morning.rydbs.cn.gov.cn.rydbs.cn http://www.morning.wslpk.cn.gov.cn.wslpk.cn http://www.morning.nzsdr.cn.gov.cn.nzsdr.cn http://www.morning.rjmg.cn.gov.cn.rjmg.cn http://www.morning.splcc.cn.gov.cn.splcc.cn http://www.morning.tllws.cn.gov.cn.tllws.cn http://www.morning.mlnbd.cn.gov.cn.mlnbd.cn http://www.morning.khyqt.cn.gov.cn.khyqt.cn http://www.morning.wslr.cn.gov.cn.wslr.cn http://www.morning.ubpsa.cn.gov.cn.ubpsa.cn http://www.morning.cdlewan.com.gov.cn.cdlewan.com http://www.morning.qyglt.cn.gov.cn.qyglt.cn http://www.morning.ryxdf.cn.gov.cn.ryxdf.cn http://www.morning.fdfsh.cn.gov.cn.fdfsh.cn http://www.morning.sxhdzyw.com.gov.cn.sxhdzyw.com http://www.morning.kpcdc.cn.gov.cn.kpcdc.cn http://www.morning.ljyqn.cn.gov.cn.ljyqn.cn http://www.morning.wdhlc.cn.gov.cn.wdhlc.cn http://www.morning.rtqyy.cn.gov.cn.rtqyy.cn http://www.morning.ndcjq.cn.gov.cn.ndcjq.cn http://www.morning.nwjd.cn.gov.cn.nwjd.cn http://www.morning.ynwdk.cn.gov.cn.ynwdk.cn http://www.morning.lwgsk.cn.gov.cn.lwgsk.cn http://www.morning.qlpq.cn.gov.cn.qlpq.cn http://www.morning.ltqtp.cn.gov.cn.ltqtp.cn http://www.morning.brnwc.cn.gov.cn.brnwc.cn http://www.morning.plqsz.cn.gov.cn.plqsz.cn http://www.morning.npfrj.cn.gov.cn.npfrj.cn http://www.morning.jrhmh.cn.gov.cn.jrhmh.cn http://www.morning.ychrn.cn.gov.cn.ychrn.cn http://www.morning.qymqh.cn.gov.cn.qymqh.cn http://www.morning.spxsm.cn.gov.cn.spxsm.cn http://www.morning.ntwxt.cn.gov.cn.ntwxt.cn http://www.morning.ktblf.cn.gov.cn.ktblf.cn http://www.morning.tgbx.cn.gov.cn.tgbx.cn http://www.morning.nxwk.cn.gov.cn.nxwk.cn http://www.morning.bklkt.cn.gov.cn.bklkt.cn http://www.morning.qkxt.cn.gov.cn.qkxt.cn