开启辅助访问 切换到宽版

精易论坛

 找回密码
 注册

QQ登录

只需一步,快速开始

用微信号发送消息登录论坛

新人指南 邀请好友注册 - 我关注人的新帖 教你赚取精币 - 每日签到


求职/招聘- 论坛接单- 开发者大厅

论坛版规 总版规 - 建议/投诉 - 应聘版主 - 精华帖总集 积分说明 - 禁言标准 - 有奖举报

查看: 206|回复: 5
收起左侧

[已解决] 图片采集

 关闭 [复制链接]
结帖率:100% (2/2)
发表于 昨天 10:38 | 显示全部楼层 |阅读模式   湖北省武汉市
10精币
大佬们,求完善,目前能正常下载,但是下载的内容是图集中的可见图片,其他图片需要登录以后才能采集的到,但是我登录了以后还是采集不到,不知道为什么,我贴一下代码,求大家帮忙看看,看能不能完善一下。
[Python] 纯文本查看 复制代码
import os
import re
import json
import requests
from bs4 import BeautifulSoup
import tkinter as tk
from tkinter import ttk, messagebox
from PIL import Image, ImageTk
from io import BytesIO
import threading
from urllib.parse import urljoin, urlparse
import webbrowser
import time
import random


class MxdBrowserPro:
    def __init__(self, root):
        self.root = root
        self.root.title("梦想岛高级下载器 v7.8")
        self.root.geometry("1200x800")

        # 确保窗口关闭时正确退出
        self.root.protocol("WM_DELETE_WINDOW", self.on_close)

        # 初始化分类
        self.categories = {
            "首页": "/",
            "每日更新": "/gallery/",
            "秀人网": "/jigou/1.html",
            "异思趣向": "/jigou/7.html",
            "ROSI写真": "/jigou/17.html",
            "丽柜": "/jigou/19.html",
            "语画界": "/jigou/593.html",
            "尤蜜荟": "/jigou/98.html",
            "克拉女神": "/jigou/1419.html",
            "爱蜜社": "/jigou/118.html",
            "美媛馆": "/jigou/1934.html",
            "花漾show": "/jigou/128.html",
            "嗲囡囡": "/jigou/830.html",
            "丝袜美腿": "/tags/siwameitui.html",
            "黑丝诱惑": "/tags/heisiyouhuo.html",
            "性感少女": "/tags/xingganshaonv.html",
            "日本少女": "/tags/ribenshaonv.html"
        }

        # 网站配置
        self.base_url = "https://www.mxd009.cc/"
        self.login_url = urljoin(self.base_url, "/e/member/doaction.php")
        self.headers = {
            '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',
            'Referer': self.base_url,
            'Origin': self.base_url,
            'Content-Type': 'application/x-www-form-urlencoded'
        }

        # 初始化数据
        self.current_page = 1
        self.max_page = 1
        self.galleries = []
        self.current_category = "首页"
        self.downloading = False
        self.logged_in = False
        self.session = requests.Session()
        self.session.headers.update(self.headers)

        # 加载保存的cookies
        self.load_cookies()

        # 设置UI
        self.setup_ui()
        self.load_homepage()

    def on_close(self):
        """窗口关闭事件处理"""
        if self.downloading:
            if not messagebox.askokcancel("退出", "当前有下载任务进行中,确定要退出吗?"):
                return
        self.root.destroy()

    def load_cookies(self):
        """加载保存的cookies"""
        if os.path.exists("mxd_cookies.json"):
            try:
                with open("mxd_cookies.json") as f:
                    cookies = json.load(f)
                    self.session.cookies.update(cookies)

                    # 验证cookies是否有效
                    test_url = urljoin(self.base_url, "/e/member/cp/")
                    response = self.session.get(test_url, timeout=10)
                    if "login" not in response.url.lower():
                        self.logged_in = True
            except Exception as e:
                print(f"加载cookies失败: {str(e)}")

    def setup_ui(self):
        """设置现代化UI界面"""
        # 主框架
        main_frame = tk.Frame(self.root, bg="#f5f5f5")
        main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)

        # 顶部控制栏
        control_frame = tk.Frame(main_frame, bg="#f5f5f5")
        control_frame.pack(fill=tk.X, pady=(0, 10))

        # 分类选择
        tk.Label(control_frame, text="分类:", bg="#f5f5f5", font=('微软雅黑', 10)).pack(side=tk.LEFT, padx=5)
        self.category_var = tk.StringVar()
        self.category_combo = ttk.Combobox(
            control_frame,
            textvariable=self.category_var,
            values=list(self.categories.keys()),
            state="readonly",
            width=18,
            font=('微软雅黑', 10)
        )
        self.category_combo.pack(side=tk.LEFT, padx=5)
        self.category_combo.current(0)
        self.category_combo.bind("<<ComboboxSelected>>", self.on_category_change)

        # 登录状态
        login_status_text = "已登录(自动)" if self.logged_in else "未登录"
        login_status_color = "green" if self.logged_in else "red"
        self.login_status = tk.Label(
            control_frame,
            text=login_status_text,
            bg="#f5f5f5",
            fg=login_status_color,
            font=('微软雅黑', 10),
            padx=10
        )
        self.login_status.pack(side=tk.LEFT, padx=10)

        # 登录按钮
        self.login_btn = ttk.Button(
            control_frame,
            text="退出登录" if self.logged_in else "登录账号",
            command=self.show_login_dialog,
            width=10
        )
        self.login_btn.pack(side=tk.LEFT, padx=5)

        # 翻页控制
        page_frame = tk.Frame(control_frame, bg="#f5f5f5")
        page_frame.pack(side=tk.LEFT, padx=20)

        style = ttk.Style()
        style.configure('TButton', padding=5, font=('微软雅黑', 9))
        style.configure('TCombobox', font=('微软雅黑', 10))

        self.prev_btn = ttk.Button(
            page_frame,
            text="◀ 上一页",
            command=self.prev_page,
            style='TButton'
        )
        self.prev_btn.pack(side=tk.LEFT, padx=2)

        self.page_label = tk.Label(
            page_frame,
            text="1/1",
            bg="#f5f5f5",
            width=10,
            font=('微软雅黑', 10)
        )
        self.page_label.pack(side=tk.LEFT)

        self.next_btn = ttk.Button(
            page_frame,
            text="下一页 ▶",
            command=self.next_page,
            style='TButton'
        )
        self.next_btn.pack(side=tk.LEFT, padx=2)

        # 下载控制
        download_frame = tk.Frame(control_frame, bg="#f5f5f5")
        download_frame.pack(side=tk.RIGHT)

        self.download_btn = ttk.Button(
            download_frame,
            text="下载选中图集",
            command=self.download_selected,
            style='TButton'
        )
        self.download_btn.pack(side=tk.LEFT, padx=5)

        self.open_folder_btn = ttk.Button(
            download_frame,
            text="打开下载目录",
            command=self.open_download_folder,
            style='TButton'
        )
        self.open_folder_btn.pack(side=tk.LEFT, padx=5)

        # 主内容区
        content_frame = tk.Frame(main_frame, bg="#ffffff", bd=1, relief=tk.SOLID)
        content_frame.pack(fill=tk.BOTH, expand=True)

        # 图集列表
        self.tree = ttk.Treeview(
            content_frame,
            columns=("title", "model", "count", "tags", "source"),
            show="headings",
            selectmode="extended",
            style='Custom.Treeview'
        )

        style.configure('Custom.Treeview', font=('微软雅黑', 10), rowheight=25)
        style.configure('Custom.Treeview.Heading', font=('微软雅黑', 10, 'bold'))

        self.tree.heading("title", text="标题")
        self.tree.heading("model", text="模特")
        self.tree.heading("count", text="图片数")
        self.tree.heading("tags", text="标签")
        self.tree.heading("source", text="来源")

        self.tree.column("title", width=350, anchor='w')
        self.tree.column("model", width=120, anchor='center')
        self.tree.column("count", width=80, anchor='center')
        self.tree.column("tags", width=200, anchor='w')
        self.tree.column("source", width=150, anchor='w')

        scrollbar = ttk.Scrollbar(content_frame, orient="vertical", command=self.tree.yview)
        self.tree.configure(yscrollcommand=scrollbar.set)

        self.tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
        self.tree.bind("<<TreeviewSelect>>", self.on_item_select)

        # 预览区域
        preview_frame = tk.Frame(content_frame, bg="#ffffff", width=380, bd=1, relief=tk.SOLID)
        preview_frame.pack_propagate(False)
        preview_frame.pack(side=tk.RIGHT, fill=tk.BOTH)

        self.preview_img = tk.Label(preview_frame, bg="#f9f9f9")
        self.preview_img.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)

        self.info_label = tk.Label(
            preview_frame,
            text="请选择图集查看详情",
            bg="#ffffff",
            fg="#666",
            font=('微软雅黑', 10),
            wraplength=360,
            justify='left'
        )
        self.info_label.pack(fill=tk.X, padx=10, pady=5)

        # 下载进度条
        self.progress_frame = tk.Frame(preview_frame, bg="#ffffff")
        self.progress_frame.pack(fill=tk.X, padx=10, pady=5)

        self.progress_label = tk.Label(
            self.progress_frame,
            text="准备下载...",
            bg="#ffffff",
            fg="#333",
            font=('微软雅黑', 9)
        )
        self.progress_label.pack(fill=tk.X)

        self.progress_bar = ttk.Progressbar(
            self.progress_frame,
            orient='horizontal',
            mode='determinate',
            length=350
        )
        self.progress_bar.pack(fill=tk.X)

        btn_frame = tk.Frame(preview_frame, bg="#ffffff")
        btn_frame.pack(fill=tk.X, padx=10, pady=10)

        self.detail_btn = ttk.Button(
            btn_frame,
            text="浏览器查看详情",
            command=self.view_details,
            style='TButton'
        )
        self.detail_btn.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=2)

        self.download_all_btn = ttk.Button(
            btn_frame,
            text="下载全部图片",
            command=self.download_selected,
            style='TButton'
        )
        self.download_all_btn.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=2)

        # 状态栏
        self.status_var = tk.StringVar()
        self.status_var.set("就绪 | 欢迎使用梦想岛图集下载器")
        status_bar = tk.Label(
            main_frame,
            textvariable=self.status_var,
            relief=tk.SUNKEN,
            anchor=tk.W,
            bg="#e0e0e0",
            fg="#333",
            font=('微软雅黑', 9)
        )
        status_bar.pack(fill=tk.X, pady=(10, 0))

    def show_login_dialog(self):
        """显示登录对话框"""
        if self.logged_in:
            self.logout()
            return

        login_dialog = tk.Toplevel(self.root)
        login_dialog.title("登录梦想岛")
        login_dialog.geometry("300x200")
        login_dialog.resizable(False, False)
        login_dialog.grab_set()
        login_dialog.transient(self.root)

        # 居中对话框
        window_width = 300
        window_height = 200
        screen_width = login_dialog.winfo_screenwidth()
        screen_height = login_dialog.winfo_screenheight()
        x = (screen_width - window_width) // 2
        y = (screen_height - window_height) // 2
        login_dialog.geometry(f"{window_width}x{window_height}+{x}+{y}")

        # 用户名
        tk.Label(login_dialog, text="用户名:", font=('微软雅黑', 10)).place(x=30, y=30)
        username_var = tk.StringVar()
        username_entry = ttk.Entry(login_dialog, textvariable=username_var, width=20)
        username_entry.place(x=100, y=30)
        username_entry.focus()

        # 密码
        tk.Label(login_dialog, text="密码:", font=('微软雅黑', 10)).place(x=30, y=70)
        password_var = tk.StringVar()
        password_entry = ttk.Entry(login_dialog, textvariable=password_var, width=20, show="*")
        password_entry.place(x=100, y=70)

        # 登录按钮
        def attempt_login():
            username = username_var.get()
            password = password_var.get()
            if not username or not password:
                messagebox.showerror("错误", "用户名和密码不能为空")
                return

            login_dialog.destroy()
            threading.Thread(target=self.perform_login, args=(username, password), daemon=True).start()

        login_btn = ttk.Button(login_dialog, text="登录", command=attempt_login)
        login_btn.place(x=120, y=120, width=80)

        # 绑定回车键
        login_dialog.bind('<Return>', lambda event: attempt_login())

    def perform_login(self, username, password):
        """执行登录操作"""
        self.status_var.set("正在登录...")
        try:
            # 1. 获取登录页面以获取必要的cookies
            login_page_url = urljoin(self.base_url, "/e/member/login/")
            login_page_response = self.session.get(login_page_url, timeout=15)
            login_page_response.raise_for_status()

            # 2. 检查是否有验证码要求
            soup = BeautifulSoup(login_page_response.text, 'html.parser')
            if soup.select_one('input[name="ecmscheck"]'):
                self.root.after(0, lambda: messagebox.showwarning(
                    "需要验证码",
                    "当前登录需要验证码,请先在浏览器中登录后再使用本程序"
                ))
                return False

            # 3. 准备登录数据
            login_data = {
                'enews': 'login',
                'username': username,
                'password': password,
                'ecmsfrom': self.base_url,
                'tobind': '0',
                'way': 'login',
                'type': 'login',
                'Submit': '登 录'
            }

            # 4. 设置正确的请求头
            headers = {
                'Content-Type': 'application/x-www-form-urlencoded',
                'Referer': login_page_url,
                'Origin': self.base_url
            }

            # 5. 发送登录请求
            response = self.session.post(
                self.login_url,
                data=login_data,
                headers=headers,
                timeout=15,
                allow_redirects=True
            )

            # 6. 验证登录结果
            if response.status_code != 200:
                raise Exception(f"HTTP状态码异常: {response.status_code}")

            # 检查是否登录成功
            if "登录成功" in response.text or "login" not in response.url.lower():
                # 7. 验证会员中心访问
                profile_url = urljoin(self.base_url, "/e/member/cp/")
                profile_response = self.session.get(profile_url, timeout=10)

                if "login" in profile_response.url.lower():
                    raise Exception("登录状态验证失败")

                # 8. 登录成功处理
                self.logged_in = True
                self.root.after(0, lambda: self.login_status.config(text=f"已登录: {username}", fg="green"))
                self.root.after(0, lambda: self.login_btn.config(text="退出登录"))
                self.status_var.set(f"登录成功: {username}")

                # 保存cookies
                with open("mxd_cookies.json", "w") as f:
                    json.dump(self.session.cookies.get_dict(), f)

                messagebox.showinfo("登录成功", "您已成功登录梦想岛")
                return True
            else:
                # 提取具体错误信息
                error_msg = "登录失败"
                soup = BeautifulSoup(response.text, 'html.parser')

                # 尝试从layui弹窗中提取错误
                script_tags = soup.find_all('script')
                for script in script_tags:
                    if script.string and 'alert' in script.string:
                        match = re.search(r'alert\(["\'](.*?)["\']\)', script.string)
                        if match:
                            error_msg = match.group(1)
                            break

                if not error_msg:
                    error_div = soup.select_one('.layui-layer-content')
                    if error_div:
                        error_msg = error_div.get_text(strip=True)

                raise Exception(error_msg or "未知登录错误")

        except Exception as e:
            error_msg = str(e)
            # 保存错误响应供调试
            with open("login_error.html", "w", encoding="utf-8") as f:
                f.write(response.text if 'response' in locals() else "No response")

            self.root.after(0, lambda: messagebox.showerror(
                "登录失败",
                f"登录失败: {error_msg}\n错误详情已保存到login_error.html"
            ))
            self.status_var.set("登录失败")
            return False

    def logout(self):
        """退出登录"""
        try:
            # 发送退出请求
            logout_url = urljoin(self.base_url, "/e/member/doaction.php?enews=exit")
            self.session.get(logout_url, timeout=10)

            # 删除cookies文件
            if os.path.exists("mxd_cookies.json"):
                os.remove("mxd_cookies.json")
        except Exception as e:
            print(f"退出登录时出错: {str(e)}")

        self.logged_in = False
        self.session = requests.Session()  # 重置会话
        self.session.headers.update(self.headers)
        self.login_status.config(text="未登录", fg="red")
        self.login_btn.config(text="登录账号")
        self.status_var.set("已退出登录")

    def load_homepage(self):
        """加载当前分类页面"""
        if self.downloading:
            return

        self.status_var.set(f"正在加载 {self.current_category}...")
        url = urljoin(self.base_url, self.categories[self.current_category])

        # 处理分页
        if self.current_page > 1:
            if "?" in url:
                url += f"&page={self.current_page}"
            else:
                url += f"page/{self.current_page}/" if not url.endswith("/") else f"page/{self.current_page}"

        threading.Thread(target=self.fetch_page, args=(url,), daemon=True).start()

    def fetch_page(self, url):
        """获取页面数据"""
        try:
            response = self.session.get(url, timeout=15)
            soup = BeautifulSoup(response.text, 'html.parser')

            # 检查是否跳转到登录页面
            if "login" in response.url:
                self.root.after(0, lambda: messagebox.showwarning(
                    "需要登录",
                    "访问此内容需要登录,请先登录账号"
                ))
                self.status_var.set("需要登录才能访问")
                return

            # 解析图集数据
            galleries = []
            gallery_items = soup.select('.databox li') or soup.select('.gallery-list li') or soup.select('.pic-list li')

            for item in gallery_items:
                try:
                    title_elem = item.select_one('.ztitle a') or item.select_one('h2 a') or item.select_one('.title a')
                    title = title_elem.text.strip() if title_elem else "无标题"

                    link = urljoin(self.base_url, title_elem['href']) if title_elem else "#"

                    model_elem = item.select_one('.chujing') or item.select_one('.model')
                    model = model_elem.text.replace('模特:', '').strip() if model_elem else "未知模特"

                    count_elem = item.select_one('.num') or item.select_one('.pic-num')
                    count = count_elem.text.strip() if count_elem else "0P"

                    tags = ' '.join([a.text for a in item.select('.rtitle a')]) or "无标签"

                    source_elem = item.select_one('.rtitle a') or item.select_one('.source a')
                    source = source_elem.text.strip() if source_elem else "未知来源"

                    cover_elem = item.select_one('.img-box img') or item.select_one('img')
                    cover = cover_elem['src'] if cover_elem and 'src' in cover_elem.attrs else ""
                    if cover and not cover.startswith(('http://', 'https://')):
                        cover = urljoin(self.base_url, cover)

                    galleries.append({
                        'title': title,
                        'link': link,
                        'model': model,
                        'count': count,
                        'tags': tags,
                        'source': source,
                        'cover': cover
                    })
                except Exception as e:
                    continue

            # 尝试获取最大页数
            pagination = soup.select_one('.pagination') or soup.select_one('.page-nav')
            if pagination:
                page_links = pagination.select('a')
                if page_links:
                    last_page = 1
                    for link in page_links:
                        if link.text.isdigit():
                            last_page = max(last_page, int(link.text))
                    self.max_page = last_page

            # 更新UI
            self.root.after(0, self.update_gallery_list, galleries)

        except Exception as e:
            error_msg = str(e)
            self.root.after(0, lambda: self.show_error(f"加载失败: {error_msg}"))

    def update_gallery_list(self, galleries):
        """更新图集列表"""
        self.tree.delete(*self.tree.get_children())
        self.galleries = galleries

        for gallery in galleries:
            self.tree.insert("", "end", values=(
                gallery['title'],
                gallery['model'],
                gallery['count'],
                gallery['tags'],
                gallery['source']
            ))

        self.status_var.set(
            f"{self.current_category} | 共 {len(galleries)} 个图集 | 第 {self.current_page}/{self.max_page} 页")
        self.update_buttons()

    def on_item_select(self, event):
        """选中图集事件"""
        selection = self.tree.selection()
        if not selection:
            return

        index = self.tree.index(selection[0])
        if index >= len(self.galleries):
            return

        self.current_selection = self.galleries[index]
        self.detail_btn.config(state=tk.NORMAL)
        self.download_all_btn.config(state=tk.NORMAL)

        # 显示信息
        info_text = f"标题: {self.current_selection['title']}\n\n"
        info_text += f"模特: {self.current_selection['model']}\n\n"
        info_text += f"图片数: {self.current_selection['count']}\n\n"
        info_text += f"标签: {self.current_selection['tags']}\n\n"
        info_text += f"来源: {self.current_selection['source']}"

        self.info_label.config(text=info_text)

        # 加载缩略图
        if self.current_selection['cover']:
            threading.Thread(target=self.load_thumbnail, args=(self.current_selection['cover'],), daemon=True).start()
        else:
            self.preview_img.config(image=None)
            self.preview_img.image = None

    def load_thumbnail(self, img_url):
        """加载缩略图"""
        try:
            response = self.session.get(img_url, timeout=10)
            img = Image.open(BytesIO(response.content))

            # 计算等比例缩放的尺寸
            width, height = img.size
            max_size = 360
            if width > height:
                new_width = max_size
                new_height = int(height * (max_size / width))
            else:
                new_height = max_size
                new_width = int(width * (max_size / height))

            img = img.resize((new_width, new_height), Image.LANCZOS)
            tk_img = ImageTk.PhotoImage(img)

            self.preview_img.config(image=tk_img)
            self.preview_img.image = tk_img
        except Exception as e:
            print(f"加载缩略图失败: {str(e)}")
            self.preview_img.config(image=None)
            self.preview_img.image = None

    def download_selected(self):
        """下载选中图集"""
        if not hasattr(self, 'current_selection'):
            messagebox.showwarning("提示", "请先选择要下载的图集")
            return

        if self.downloading:
            messagebox.showwarning("提示", "当前有下载任务正在进行")
            return

        if not self.logged_in:
            messagebox.showwarning("需要登录", "下载功能需要登录后才能使用")
            return

        gallery = self.current_selection
        confirm = messagebox.askyesno(
            "确认下载",
            f"确定要下载图集:\n{gallery['title']}\n模特: {gallery['model']}\n共 {gallery['count']} 张图片吗?"
        )
        if confirm:
            self.downloading = True
            self.update_buttons()
            threading.Thread(target=self.download_gallery, args=(gallery,), daemon=True).start()

    def download_gallery(self, gallery):
        """下载图集所有图片 - 基于CDN直接构建URL的优化逻辑"""
        try:
            # 创建保存目录
            save_dir = os.path.join("downloads", self.sanitize_filename(f"{gallery['source']}_{gallery['title']}"))
            os.makedirs(save_dir, exist_ok=True)

            # 获取图集详情页
            detail_response = self.session.get(gallery['link'], timeout=20)

            # 检查是否登录状态
            if "login" in detail_response.url.lower():
                self.root.after(0, lambda: messagebox.showwarning(
                    "需要登录",
                    "下载此图集需要登录,请先登录账号"
                ))
                self.downloading = False
                self.root.after(0, self.update_buttons)
                return

            detail_soup = BeautifulSoup(detail_response.text, 'html.parser')

            # 提取CDN域名列表
            cdn_domains = set()
            for script in detail_soup.find_all('script'):
                if script.string:
                    # 查找可能的CDN域名
                    cdn_matches = re.findall(r'https?://(oss-img-mmxxdd\.[^/"\']+)', script.string)
                    for match in cdn_matches:
                        cdn_domains.add(match)

            # 如果没找到CDN域名,使用默认值
            if not cdn_domains:
                cdn_domains = {
                    "oss-img-mmxxdd.ojbkcdn.cc",
                    "oss-img-mmxxdd.mengguzhiai.com"
                }

            # 解析总页数
            total_pages = 1
            pagination = detail_soup.select_one('.pagination') or detail_soup.select_one('.page-nav')
            if pagination:
                # 尝试从文本中提取总页数(如"共10页")
                page_text = pagination.get_text(strip=True)
                match = re.search(r'共(\d+)页', page_text)
                if match:
                    total_pages = int(match.group(1))
                else:
                    # 尝试从最后一页按钮提取
                    last_page_link = pagination.find('a', text=re.compile(r'末页|尾页|最后'))
                    if last_page_link and 'href' in last_page_link.attrs:
                        href = last_page_link['href']
                        match = re.search(r'_(\d+)\.html', href)
                        if match:
                            total_pages = int(match.group(1)) + 1  # 因为是从0开始计数

            # 尝试从页面提取日期部分
            date_part = None
            date_match = re.search(r'upload/images/(\d+)/', detail_response.text)
            if date_match:
                date_part = date_match.group(1)

            # 生成所有可能的图片URL
            img_urls = []

            # 方法1: 尝试直接从CDN构建URL
            if date_part:
                for cdn_domain in cdn_domains:
                    for i in range(total_pages):
                        img_url = f"https://{cdn_domain}/upload/images/{date_part}/{i}.jpg"
                        img_urls.append(img_url)

            # 方法2: 如果方法1失败,尝试从分页提取
            if not img_urls:
                base_url = gallery['link'].rsplit('_', 1)[0]  # 处理带_0.html的URL
                page_urls = []

                for page in range(0, total_pages):
                    page_url = f"{base_url}_{page}.html" if '_' in gallery['link'] else f"{base_url}_{page}.html"
                    page_urls.append(urljoin(self.base_url, page_url))

                # 提取所有图片URL
                for idx, page_url in enumerate(page_urls, 1):
                    try:
                        # 添加随机延迟,避免请求过快
                        time.sleep(random.uniform(0.5, 1.5))

                        page_response = self.session.get(page_url, timeout=15)
                        page_soup = BeautifulSoup(page_response.text, 'html.parser')

                        # 尝试多种选择器提取图片
                        img_tag = (
                                page_soup.select_one('.gallerypic img') or
                                page_soup.select_one('.img-responsive') or
                                page_soup.select_one('.main-img img') or
                                page_soup.select_one('.content img')
                        )

                        if img_tag and 'src' in img_tag.attrs:
                            img_url = img_tag['src']
                            if not img_url.startswith(('http:', 'https:')):
                                img_url = urljoin(self.base_url, img_url)
                            img_urls.append(img_url)
                            self.root.after(0, lambda: self.status_var.set(f"已提取第{idx}/{total_pages}页图片"))
                        else:
                            self.root.after(0, lambda: self.status_var.set(f"第{idx}页未找到图片,尝试从JS提取"))

                            # 尝试从JavaScript中提取
                            scripts = page_soup.find_all('script')
                            for script in scripts:
                                if script.string:
                                    # 查找img_url变量
                                    match = re.search(r'var\s+img_url\s*=\s*["\']([^"\']+)["\']', script.string)
                                    if match:
                                        img_url = match.group(1)
                                        if not img_url.startswith(('http:', 'https:')):
                                            img_url = urljoin(self.base_url, img_url)
                                        img_urls.append(img_url)
                                        self.root.after(0, lambda: self.status_var.set(
                                            f"从JS提取第{idx}/{total_pages}页图片"))
                                        break

                                    # 尝试其他可能的JS变量名
                                    match = re.search(r'var\s+imgSrc\s*=\s*["\']([^"\']+)["\']', script.string)
                                    if match:
                                        img_url = match.group(1)
                                        if not img_url.startswith(('http:', 'https:')):
                                            img_url = urljoin(self.base_url, img_url)
                                        img_urls.append(img_url)
                                        self.root.after(0, lambda: self.status_var.set(
                                            f"从JS提取第{idx}/{total_pages}页图片"))
                                        break
                    except Exception as e:
                        self.root.after(0, lambda: self.status_var.set(f"提取第{idx}页图片失败: {str(e)}"))
                        continue

            # 去重
            img_urls = list(set(img_urls))

            # 保存下载日志
            with open(os.path.join(save_dir, "download_log.txt"), "w", encoding="utf-8") as f:
                f.write(f"图集标题: {gallery['title']}\n")
                f.write(f"模特: {gallery['model']}\n")
                f.write(f"来源: {gallery['source']}\n")
                f.write(f"总图片数: {len(img_urls)}\n\n")
                f.write("图片URL列表:\n")
                for url in img_urls:
                    f.write(url + "\n")

            # 下载图片
            total = len(img_urls)
            success_count = 0
            failed_count = 0

            for i, url in enumerate(img_urls, 1):
                try:
                    # 显示进度
                    progress = int((i / total) * 100)
                    self.root.after(0, lambda p=progress: self.progress_bar.config(value=p))
                    self.root.after(0, lambda m=f"下载中: {i}/{total}": self.progress_label.config(text=m))

                    # 尝试多个CDN域名
                    base_url = url.split('/upload')[0]
                    path = url.split('/upload')[1]
                    cdn_tried = []

                    for cdn_domain in cdn_domains:
                        cdn_tried.append(cdn_domain)
                        try_url = f"{cdn_domain}/upload{path}"

                        response = self.session.get(try_url, timeout=30)
                        if response.status_code == 200 and 'image' in response.headers.get('Content-Type', ''):
                            # 保存图片
                            ext = os.path.splitext(try_url)[1] or '.jpg'
                            filename = f"{i:03d}{ext}"
                            with open(os.path.join(save_dir, filename), 'wb') as f:
                                f.write(response.content)

                            success_count += 1
                            self.root.after(0, lambda: self.status_var.set(f"下载成功: {filename} ({i}/{total})"))
                            break

                    # 如果所有CDN都失败
                    if i == len(img_urls) and success_count == 0:
                        raise Exception(f"所有CDN域名尝试失败: {cdn_tried}")

                    # 添加随机延迟,避免请求过快
                    time.sleep(random.uniform(0.5, 1.5))

                except Exception as e:
                    failed_count += 1
                    self.root.after(0, lambda: self.status_var.set(f"下载失败 {i}/{total}: {str(e)}"))

            # 下载完成
            self.root.after(0, lambda: self.progress_bar.config(value=100))
            self.root.after(0, lambda: self.progress_label.config(
                text=f"下载完成: {success_count} 成功, {failed_count} 失败"))
            self.root.after(0, lambda: self.status_var.set(f"图集下载完成: {gallery['title']}"))

            # 显示结果
            result_msg = f"图集下载完成!\n成功: {success_count} 张\n失败: {failed_count} 张\n\n保存路径: {os.path.abspath(save_dir)}"
            self.root.after(0, lambda: messagebox.showinfo("下载完成", result_msg))

        except Exception as e:
            self.root.after(0, lambda: messagebox.showerror("下载错误", f"下载失败: {str(e)}"))
            self.root.after(0, lambda: self.status_var.set("下载失败"))
        finally:
            self.downloading = False
            self.root.after(0, self.update_buttons)

    def sanitize_filename(self, filename):
        """清理文件名,移除不合法字符"""
        invalid_chars = r'[\\/:*?"<>|]'
        return re.sub(invalid_chars, '_', filename)

    def update_buttons(self):
        """更新按钮状态"""
        self.prev_btn.config(state=tk.NORMAL if self.current_page > 1 and not self.downloading else tk.DISABLED)
        self.next_btn.config(
            state=tk.NORMAL if self.current_page < self.max_page and not self.downloading else tk.DISABLED)
        self.page_label.config(text=f"{self.current_page}/{self.max_page}")

        if hasattr(self, 'detail_btn'):
            self.detail_btn.config(
                state=tk.NORMAL if hasattr(self, 'current_selection') and not self.downloading else tk.DISABLED)
            self.download_all_btn.config(
                state=tk.NORMAL if hasattr(self, 'current_selection') and not self.downloading else tk.DISABLED)
            self.download_btn.config(
                state=tk.NORMAL if hasattr(self, 'current_selection') and not self.downloading else tk.DISABLED)

    def prev_page(self):
        """上一页"""
        if self.current_page > 1 and not self.downloading:
            self.current_page -= 1
            self.load_homepage()

    def next_page(self):
        """下一页"""
        if self.current_page < self.max_page and not self.downloading:
            self.current_page += 1
            self.load_homepage()

    def on_category_change(self, event):
        """分类变更事件"""
        self.current_category = self.category_var.get()
        self.current_page = 1
        self.load_homepage()

    def view_details(self):
        """在浏览器中查看详情"""
        if hasattr(self, 'current_selection'):
            webbrowser.open(self.current_selection['link'])

    def open_download_folder(self):
        """打开下载目录"""
        if not os.path.exists("downloads"):
            os.makedirs("downloads")

        # 根据操作系统选择打开方式
        if os.name == 'nt':  # Windows
            os.system('start downloads')
        elif os.name == 'posix':  # macOS/Linux
            os.system('open downloads')

    def show_error(self, message):
        """显示错误消息"""
        messagebox.showerror("错误", message)
        self.status_var.set("就绪")


if __name__ == "__main__":
    root = tk.Tk()
    app = MxdBrowserPro(root)
    root.mainloop()

目前的样子

目前的样子

最佳答案

查看完整内容

因为cdn会导致多出一个,最后再排个序

回答提醒:如果本帖被关闭无法回复,您有更好的答案帮助楼主解决,请发表至 源码区 可获得加分喔。
友情提醒:本版被采纳的主题可在 申请荣誉值 页面申请荣誉值,获得 1点 荣誉值,荣誉值可兑换荣誉会员、终身vip用户组。
快捷通道:申请荣誉值无答案申请取消悬赏投诉有答案未采纳为最佳
结帖率:81% (26/32)

签到天数: 2 天

发表于 昨天 10:38 | 显示全部楼层   江西省吉安市
main.zip (9.28 KB, 下载次数: 2)
回复

使用道具 举报

签到天数: 3 天

发表于 昨天 11:36 | 显示全部楼层   河南省驻马店市
+V  给你看看。
回复

使用道具 举报

结帖率:81% (26/32)

签到天数: 2 天

发表于 昨天 13:17 | 显示全部楼层   江西省吉安市
[Python] 纯文本查看 复制代码
import os
import re
import json
import requests
from bs4 import BeautifulSoup
import tkinter as tk
from tkinter import ttk, messagebox
from PIL import Image, ImageTk
from io import BytesIO
import threading
from urllib.parse import urljoin, urlparse
import webbrowser
import time
import random


class MxdBrowserPro:
    def __init__(self, root):
        self.root = root
        self.root.title("梦想岛高级下载器 v7.8")
        self.root.geometry("1200x800")

        # 确保窗口关闭时正确退出
        self.root.protocol("WM_DELETE_WINDOW", self.on_close)

        # 初始化分类
        self.categories = {
            "首页": "/",
            "每日更新": "/gallery/",
            "秀人网": "/jigou/1.html",
            "异思趣向": "/jigou/7.html",
            "ROSI写真": "/jigou/17.html",
            "丽柜": "/jigou/19.html",
            "语画界": "/jigou/593.html",
            "尤蜜荟": "/jigou/98.html",
            "克拉女神": "/jigou/1419.html",
            "爱蜜社": "/jigou/118.html",
            "美媛馆": "/jigou/1934.html",
            "花漾show": "/jigou/128.html",
            "嗲囡囡": "/jigou/830.html",
            "丝袜美腿": "/tags/siwameitui.html",
            "黑丝诱惑": "/tags/heisiyouhuo.html",
            "性感少女": "/tags/xingganshaonv.html",
            "日本少女": "/tags/ribenshaonv.html"
        }

        # 网站配置
        self.base_url = "https://www.mxd009.cc/"
        self.login_url = urljoin(self.base_url, "/e/member/doaction.php")
        self.headers = {
            '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',
            'Referer': self.base_url,
            'Origin': self.base_url,
            'Content-Type': 'application/x-www-form-urlencoded'
        }

        # 初始化数据
        self.current_page = 1
        self.max_page = 1
        self.galleries = []
        self.current_category = "首页"
        self.downloading = False
        self.logged_in = False
        self.session = requests.Session()
        self.session.headers.update(self.headers)

        # 加载保存的cookies
        self.load_cookies()

        # 设置UI
        self.setup_ui()
        self.load_homepage()

    def on_close(self):
        """窗口关闭事件处理"""
        if self.downloading:
            if not messagebox.askokcancel("退出", "当前有下载任务进行中,确定要退出吗?"):
                return
        self.root.destroy()

    def load_cookies(self):
        """加载保存的cookies"""
        if os.path.exists("mxd_cookies.json"):
            try:
                with open("mxd_cookies.json") as f:
                    cookies = json.load(f)
                    self.session.cookies.update(cookies)

                    # 验证cookies是否有效
                    test_url = urljoin(self.base_url, "/e/member/cp/")
                    response = self.session.get(test_url, timeout=10)
                    if "login" not in response.url.lower():
                        self.logged_in = True
            except Exception as e:
                print(f"加载cookies失败: {str(e)}")

    def setup_ui(self):
        """设置现代化UI界面"""
        # 主框架
        main_frame = tk.Frame(self.root, bg="#f5f5f5")
        main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)

        # 顶部控制栏
        control_frame = tk.Frame(main_frame, bg="#f5f5f5")
        control_frame.pack(fill=tk.X, pady=(0, 10))

        # 分类选择
        tk.Label(control_frame, text="分类:", bg="#f5f5f5", font=('微软雅黑', 10)).pack(side=tk.LEFT, padx=5)
        self.category_var = tk.StringVar()
        self.category_combo = ttk.Combobox(
            control_frame,
            textvariable=self.category_var,
            values=list(self.categories.keys()),
            state="readonly",
            width=18,
            font=('微软雅黑', 10)
        )
        self.category_combo.pack(side=tk.LEFT, padx=5)
        self.category_combo.current(0)
        self.category_combo.bind("<<ComboboxSelected>>", self.on_category_change)

        # 登录状态
        login_status_text = "已登录(自动)" if self.logged_in else "未登录"
        login_status_color = "green" if self.logged_in else "red"
        self.login_status = tk.Label(
            control_frame,
            text=login_status_text,
            bg="#f5f5f5",
            fg=login_status_color,
            font=('微软雅黑', 10),
            padx=10
        )
        self.login_status.pack(side=tk.LEFT, padx=10)

        # 登录按钮
        self.login_btn = ttk.Button(
            control_frame,
            text="退出登录" if self.logged_in else "登录账号",
            command=self.show_login_dialog,
            width=10
        )
        self.login_btn.pack(side=tk.LEFT, padx=5)

        # 翻页控制
        page_frame = tk.Frame(control_frame, bg="#f5f5f5")
        page_frame.pack(side=tk.LEFT, padx=20)

        style = ttk.Style()
        style.configure('TButton', padding=5, font=('微软雅黑', 9))
        style.configure('TCombobox', font=('微软雅黑', 10))

        self.prev_btn = ttk.Button(
            page_frame,
            text="◀ 上一页",
            command=self.prev_page,
            style='TButton'
        )
        self.prev_btn.pack(side=tk.LEFT, padx=2)

        self.page_label = tk.Label(
            page_frame,
            text="1/1",
            bg="#f5f5f5",
            width=10,
            font=('微软雅黑', 10)
        )
        self.page_label.pack(side=tk.LEFT)

        self.next_btn = ttk.Button(
            page_frame,
            text="下一页 ▶",
            command=self.next_page,
            style='TButton'
        )
        self.next_btn.pack(side=tk.LEFT, padx=2)

        # 下载控制
        download_frame = tk.Frame(control_frame, bg="#f5f5f5")
        download_frame.pack(side=tk.RIGHT)

        self.download_btn = ttk.Button(
            download_frame,
            text="下载选中图集",
            command=self.download_selected,
            style='TButton'
        )
        self.download_btn.pack(side=tk.LEFT, padx=5)

        self.open_folder_btn = ttk.Button(
            download_frame,
            text="打开下载目录",
            command=self.open_download_folder,
            style='TButton'
        )
        self.open_folder_btn.pack(side=tk.LEFT, padx=5)

        # 主内容区
        content_frame = tk.Frame(main_frame, bg="#ffffff", bd=1, relief=tk.SOLID)
        content_frame.pack(fill=tk.BOTH, expand=True)

        # 图集列表
        self.tree = ttk.Treeview(
            content_frame,
            columns=("title", "model", "count", "tags", "source"),
            show="headings",
            selectmode="extended",
            style='Custom.Treeview'
        )

        style.configure('Custom.Treeview', font=('微软雅黑', 10), rowheight=25)
        style.configure('Custom.Treeview.Heading', font=('微软雅黑', 10, 'bold'))

        self.tree.heading("title", text="标题")
        self.tree.heading("model", text="模特")
        self.tree.heading("count", text="图片数")
        self.tree.heading("tags", text="标签")
        self.tree.heading("source", text="来源")

        self.tree.column("title", width=350, anchor='w')
        self.tree.column("model", width=120, anchor='center')
        self.tree.column("count", width=80, anchor='center')
        self.tree.column("tags", width=200, anchor='w')
        self.tree.column("source", width=150, anchor='w')

        scrollbar = ttk.Scrollbar(content_frame, orient="vertical", command=self.tree.yview)
        self.tree.configure(yscrollcommand=scrollbar.set)

        self.tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
        self.tree.bind("<<TreeviewSelect>>", self.on_item_select)

        # 预览区域
        preview_frame = tk.Frame(content_frame, bg="#ffffff", width=380, bd=1, relief=tk.SOLID)
        preview_frame.pack_propagate(False)
        preview_frame.pack(side=tk.RIGHT, fill=tk.BOTH)

        self.preview_img = tk.Label(preview_frame, bg="#f9f9f9")
        self.preview_img.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)

        self.info_label = tk.Label(
            preview_frame,
            text="请选择图集查看详情",
            bg="#ffffff",
            fg="#666",
            font=('微软雅黑', 10),
            wraplength=360,
            justify='left'
        )
        self.info_label.pack(fill=tk.X, padx=10, pady=5)

        # 下载进度条
        self.progress_frame = tk.Frame(preview_frame, bg="#ffffff")
        self.progress_frame.pack(fill=tk.X, padx=10, pady=5)

        self.progress_label = tk.Label(
            self.progress_frame,
            text="准备下载...",
            bg="#ffffff",
            fg="#333",
            font=('微软雅黑', 9)
        )
        self.progress_label.pack(fill=tk.X)

        self.progress_bar = ttk.Progressbar(
            self.progress_frame,
            orient='horizontal',
            mode='determinate',
            length=350
        )
        self.progress_bar.pack(fill=tk.X)

        btn_frame = tk.Frame(preview_frame, bg="#ffffff")
        btn_frame.pack(fill=tk.X, padx=10, pady=10)

        self.detail_btn = ttk.Button(
            btn_frame,
            text="浏览器查看详情",
            command=self.view_details,
            style='TButton'
        )
        self.detail_btn.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=2)

        self.download_all_btn = ttk.Button(
            btn_frame,
            text="下载全部图片",
            command=self.download_selected,
            style='TButton'
        )
        self.download_all_btn.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=2)

        # 状态栏
        self.status_var = tk.StringVar()
        self.status_var.set("就绪 | 欢迎使用梦想岛图集下载器")
        status_bar = tk.Label(
            main_frame,
            textvariable=self.status_var,
            relief=tk.SUNKEN,
            anchor=tk.W,
            bg="#e0e0e0",
            fg="#333",
            font=('微软雅黑', 9)
        )
        status_bar.pack(fill=tk.X, pady=(10, 0))

    def show_login_dialog(self):
        """显示登录对话框"""
        if self.logged_in:
            self.logout()
            return

        login_dialog = tk.Toplevel(self.root)
        login_dialog.title("登录梦想岛")
        login_dialog.geometry("300x200")
        login_dialog.resizable(False, False)
        login_dialog.grab_set()
        login_dialog.transient(self.root)

        # 居中对话框
        window_width = 300
        window_height = 200
        screen_width = login_dialog.winfo_screenwidth()
        screen_height = login_dialog.winfo_screenheight()
        x = (screen_width - window_width) // 2
        y = (screen_height - window_height) // 2
        login_dialog.geometry(f"{window_width}x{window_height}+{x}+{y}")

        # 用户名
        tk.Label(login_dialog, text="用户名:", font=('微软雅黑', 10)).place(x=30, y=30)
        username_var = tk.StringVar()
        username_entry = ttk.Entry(login_dialog, textvariable=username_var, width=20)
        username_entry.place(x=100, y=30)
        username_entry.focus()

        # 密码
        tk.Label(login_dialog, text="密码:", font=('微软雅黑', 10)).place(x=30, y=70)
        password_var = tk.StringVar()
        password_entry = ttk.Entry(login_dialog, textvariable=password_var, width=20, show="*")
        password_entry.place(x=100, y=70)

        # 登录按钮
        def attempt_login():
            username = username_var.get()
            password = password_var.get()
            if not username or not password:
                messagebox.showerror("错误", "用户名和密码不能为空")
                return

            login_dialog.destroy()
            threading.Thread(target=self.perform_login, args=(username, password), daemon=True).start()

        login_btn = ttk.Button(login_dialog, text="登录", command=attempt_login)
        login_btn.place(x=120, y=120, width=80)

        # 绑定回车键
        login_dialog.bind('<Return>', lambda event: attempt_login())

    def perform_login(self, username, password):
        """执行登录操作"""
        self.status_var.set("正在登录...")
        try:
            # 1. 获取登录页面以获取必要的cookies
            login_page_url = urljoin(self.base_url, "/e/member/login/")
            login_page_response = self.session.get(login_page_url, timeout=15)
            login_page_response.raise_for_status()

            # 2. 检查是否有验证码要求
            soup = BeautifulSoup(login_page_response.text, 'html.parser')
            if soup.select_one('input[name="ecmscheck"]'):
                self.root.after(0, lambda: messagebox.showwarning(
                    "需要验证码",
                    "当前登录需要验证码,请先在浏览器中登录后再使用本程序"
                ))
                return False

            # 3. 准备登录数据
            login_data = {
                'enews': 'login',
                'username': username,
                'password': password,
                'ecmsfrom': self.base_url,
                'tobind': '0',
                'way': 'login',
                'type': 'login',
                'Submit': '登 录'
            }

            # 4. 设置正确的请求头
            headers = {
                'Content-Type': 'application/x-www-form-urlencoded',
                'Referer': login_page_url,
                'Origin': self.base_url
            }

            # 5. 发送登录请求
            response = self.session.post(
                self.login_url,
                data=login_data,
                headers=headers,
                timeout=15,
                allow_redirects=True
            )

            # 6. 验证登录结果
            if response.status_code != 200:
                raise Exception(f"HTTP状态码异常: {response.status_code}")

            # 检查是否登录成功
            if "登录成功" in response.text or "login" not in response.url.lower():
                # 7. 验证会员中心访问
                profile_url = urljoin(self.base_url, "/e/member/cp/")
                profile_response = self.session.get(profile_url, timeout=10)

                if "login" in profile_response.url.lower():
                    raise Exception("登录状态验证失败")

                # 8. 登录成功处理
                self.logged_in = True
                self.root.after(0, lambda: self.login_status.config(text=f"已登录: {username}", fg="green"))
                self.root.after(0, lambda: self.login_btn.config(text="退出登录"))
                self.status_var.set(f"登录成功: {username}")

                # 保存cookies
                with open("mxd_cookies.json", "w") as f:
                    json.dump(self.session.cookies.get_dict(), f)

                messagebox.showinfo("登录成功", "您已成功登录梦想岛")

                self.load_cookies()
                return True
            else:
                # 提取具体错误信息
                error_msg = "登录失败"
                soup = BeautifulSoup(response.text, 'html.parser')

                # 尝试从layui弹窗中提取错误
                script_tags = soup.find_all('script')
                for script in script_tags:
                    if script.string and 'alert' in script.string:
                        match = re.search(r'alert\(["\'](.*?)["\']\)', script.string)
                        if match:
                            error_msg = match.group(1)
                            break

                if not error_msg:
                    error_div = soup.select_one('.layui-layer-content')
                    if error_div:
                        error_msg = error_div.get_text(strip=True)

                raise Exception(error_msg or "未知登录错误")

        except Exception as e:
            error_msg = str(e)
            # 保存错误响应供调试
            with open("login_error.html", "w", encoding="utf-8") as f:
                f.write(response.text if 'response' in locals() else "No response")

            self.root.after(0, lambda: messagebox.showerror(
                "登录失败",
                f"登录失败: {error_msg}\n错误详情已保存到login_error.html"
            ))
            self.status_var.set("登录失败")
            return False

    def logout(self):
        """退出登录"""
        try:
            # 发送退出请求
            logout_url = urljoin(self.base_url, "/e/member/doaction.php?enews=exit")
            self.session.get(logout_url, timeout=10)

            # 删除cookies文件
            if os.path.exists("mxd_cookies.json"):
                os.remove("mxd_cookies.json")
        except Exception as e:
            print(f"退出登录时出错: {str(e)}")

        self.logged_in = False
        self.session = requests.Session()  # 重置会话
        self.session.headers.update(self.headers)
        self.login_status.config(text="未登录", fg="red")
        self.login_btn.config(text="登录账号")
        self.status_var.set("已退出登录")

    def load_homepage(self):
        """加载当前分类页面"""
        if self.downloading:
            return

        self.status_var.set(f"正在加载 {self.current_category}...")
        url = urljoin(self.base_url, self.categories[self.current_category])

        # 处理分页
        if self.current_page > 1:
            if "?" in url:
                url += f"&page={self.current_page}"
            else:
                url += f"page/{self.current_page}/" if not url.endswith("/") else f"page/{self.current_page}"

        threading.Thread(target=self.fetch_page, args=(url,), daemon=True).start()

    def fetch_page(self, url):
        """获取页面数据"""
        try:
            response = self.session.get(url, timeout=15)
            soup = BeautifulSoup(response.text, 'html.parser')

            # 检查是否跳转到登录页面
            if "login" in response.url:
                self.root.after(0, lambda: messagebox.showwarning(
                    "需要登录",
                    "访问此内容需要登录,请先登录账号"
                ))
                self.status_var.set("需要登录才能访问")
                return

            # 解析图集数据
            galleries = []
            gallery_items = soup.select('.databox li') or soup.select('.gallery-list li') or soup.select('.pic-list li')

            for item in gallery_items:
                try:
                    title_elem = item.select_one('.ztitle a') or item.select_one('h2 a') or item.select_one('.title a')
                    title = title_elem.text.strip() if title_elem else "无标题"

                    link = urljoin(self.base_url, title_elem['href']) if title_elem else "#"

                    model_elem = item.select_one('.chujing') or item.select_one('.model')
                    model = model_elem.text.replace('模特:', '').strip() if model_elem else "未知模特"

                    count_elem = item.select_one('.num') or item.select_one('.pic-num')
                    count = count_elem.text.strip() if count_elem else "0P"

                    tags = ' '.join([a.text for a in item.select('.rtitle a')]) or "无标签"

                    source_elem = item.select_one('.rtitle a') or item.select_one('.source a')
                    source = source_elem.text.strip() if source_elem else "未知来源"

                    cover_elem = item.select_one('.img-box img') or item.select_one('img')
                    cover = cover_elem['src'] if cover_elem and 'src' in cover_elem.attrs else ""
                    if cover and not cover.startswith(('http://', 'https://')):
                        cover = urljoin(self.base_url, cover)

                    galleries.append({
                        'title': title,
                        'link': link,
                        'model': model,
                        'count': count,
                        'tags': tags,
                        'source': source,
                        'cover': cover
                    })
                except Exception as e:
                    continue

            # 尝试获取最大页数
            pagination = soup.select_one('.pagination') or soup.select_one('.page-nav')
            if pagination:
                page_links = pagination.select('a')
                if page_links:
                    last_page = 1
                    for link in page_links:
                        if link.text.isdigit():
                            last_page = max(last_page, int(link.text))
                    self.max_page = last_page

            # 更新UI
            self.root.after(0, self.update_gallery_list, galleries)

        except Exception as e:
            error_msg = str(e)
            self.root.after(0, lambda: self.show_error(f"加载失败: {error_msg}"))

    def update_gallery_list(self, galleries):
        """更新图集列表"""
        self.tree.delete(*self.tree.get_children())
        self.galleries = galleries

        for gallery in galleries:
            self.tree.insert("", "end", values=(
                gallery['title'],
                gallery['model'],
                gallery['count'],
                gallery['tags'],
                gallery['source']
            ))

        self.status_var.set(
            f"{self.current_category} | 共 {len(galleries)} 个图集 | 第 {self.current_page}/{self.max_page} 页")
        self.update_buttons()

    def on_item_select(self, event):
        """选中图集事件"""
        selection = self.tree.selection()
        if not selection:
            return

        index = self.tree.index(selection[0])
        if index >= len(self.galleries):
            return

        self.current_selection = self.galleries[index]
        self.detail_btn.config(state=tk.NORMAL)
        self.download_all_btn.config(state=tk.NORMAL)

        # 显示信息
        info_text = f"标题: {self.current_selection['title']}\n\n"
        info_text += f"模特: {self.current_selection['model']}\n\n"
        info_text += f"图片数: {self.current_selection['count']}\n\n"
        info_text += f"标签: {self.current_selection['tags']}\n\n"
        info_text += f"来源: {self.current_selection['source']}"

        self.info_label.config(text=info_text)

        # 加载缩略图
        if self.current_selection['cover']:
            threading.Thread(target=self.load_thumbnail, args=(self.current_selection['cover'],), daemon=True).start()
        else:
            self.preview_img.config(image=None)
            self.preview_img.image = None

    def load_thumbnail(self, img_url):
        """加载缩略图"""
        try:
            response = self.session.get(img_url, timeout=10)
            img = Image.open(BytesIO(response.content))

            # 计算等比例缩放的尺寸
            width, height = img.size
            max_size = 360
            if width > height:
                new_width = max_size
                new_height = int(height * (max_size / width))
            else:
                new_height = max_size
                new_width = int(width * (max_size / height))

            img = img.resize((new_width, new_height), Image.LANCZOS)
            tk_img = ImageTk.PhotoImage(img)

            self.preview_img.config(image=tk_img)
            self.preview_img.image = tk_img
        except Exception as e:
            print(f"加载缩略图失败: {str(e)}")
            self.preview_img.config(image=None)
            self.preview_img.image = None

    def download_selected(self):
        """下载选中图集"""
        if not hasattr(self, 'current_selection'):
            messagebox.showwarning("提示", "请先选择要下载的图集")
            return

        if self.downloading:
            messagebox.showwarning("提示", "当前有下载任务正在进行")
            return

        if not self.logged_in:
            messagebox.showwarning("需要登录", "下载功能需要登录后才能使用")
            return

        gallery = self.current_selection
        confirm = messagebox.askyesno(
            "确认下载",
            f"确定要下载图集:\n{gallery['title']}\n模特: {gallery['model']}\n共 {gallery['count']} 张图片吗?"
        )
        if confirm:
            self.downloading = True
            self.update_buttons()
            threading.Thread(target=self.download_gallery, args=(gallery,), daemon=True).start()

    def download_gallery(self, gallery):
        """下载图集所有图片 - 基于CDN直接构建URL的优化逻辑"""
        try:
            # 创建保存目录
            save_dir = os.path.join("downloads", self.sanitize_filename(f"{gallery['source']}_{gallery['title']}"))
            os.makedirs(save_dir, exist_ok=True)

            # 获取图集详情页
            detail_response = self.session.get(gallery['link'], timeout=20)

            # 检查是否登录状态
            if "login" in detail_response.url.lower():
                self.root.after(0, lambda: messagebox.showwarning(
                    "需要登录",
                    "下载此图集需要登录,请先登录账号"
                ))
                self.downloading = False
                self.root.after(0, self.update_buttons)
                return

            detail_soup = BeautifulSoup(detail_response.text, 'html.parser')

            # 提取CDN域名列表
            cdn_domains = set()
            for script in detail_soup.find_all('script'):
                if script.string:
                    # 查找可能的CDN域名
                    cdn_matches = re.findall(r'https?://(oss-img-mmxxdd\.[^/"\']+)', script.string)
                    for match in cdn_matches:
                        cdn_domains.add(match)

            # 如果没找到CDN域名,使用默认值
            if not cdn_domains:
                cdn_domains = {
                    "oss-img-mmxxdd.ojbkcdn.cc",
                    "oss-img-mmxxdd.mengguzhiai.com"
                }

            # 解析总页数
            total_pages = int(gallery['count'][:-1])
            # pagination = detail_soup.select_one('.pagination') or detail_soup.select_one('.page-nav')
            # if pagination:
            #     # 尝试从文本中提取总页数(如"共10页")
            #     page_text = pagination.get_text(strip=True)
            #     match = re.search(r'共(\d+)页', page_text)
            #     if match:
            #         total_pages = int(match.group(1))
            #     else:
            #         # 尝试从最后一页按钮提取
            #         last_page_link = pagination.find('a', text=re.compile(r'末页|尾页|最后'))
            #         if last_page_link and 'href' in last_page_link.attrs:
            #             href = last_page_link['href']
            #             match = re.search(r'_(\d+)\.html', href)
            #             if match:
            #                 total_pages = int(match.group(1)) + 1  # 因为是从0开始计数

            # 尝试从页面提取日期部分
            date_part = None
            date_match = re.search(r'upload/images/(\d+)/', detail_response.text)
            if date_match:
                date_part = date_match.group(1)

            # 生成所有可能的图片URL
            img_urls = []

            # 方法1: 尝试直接从CDN构建URL
            if date_part:
                for cdn_domain in cdn_domains:
                    for i in range(total_pages):
                        img_url = f"https://{cdn_domain}/upload/images/{date_part}/{i}.jpg"
                        img_urls.append(img_url)
            print(img_urls)

            # 方法2: 如果方法1失败,尝试从分页提取
            if not img_urls:
                base_url = gallery['link'].rsplit('_', 1)[0]  # 处理带_0.html的URL
                page_urls = []

                for page in range(0, total_pages):
                    page_url = f"{base_url}_{page}.html" if '_' in gallery['link'] else f"{base_url}_{page}.html"
                    page_urls.append(urljoin(self.base_url, page_url))

                # 提取所有图片URL
                print(page_urls)
                for idx, page_url in enumerate(page_urls, 1):
                    try:
                        # 添加随机延迟,避免请求过快
                        time.sleep(random.uniform(0.5, 1.5))

                        page_response = self.session.get(page_url, timeout=15)
                        page_soup = BeautifulSoup(page_response.text, 'html.parser')

                        # 尝试多种选择器提取图片
                        img_tag = (
                                page_soup.select_one('.gallerypic img') or
                                page_soup.select_one('.img-responsive') or
                                page_soup.select_one('.main-img img') or
                                page_soup.select_one('.content img')
                        )

                        if img_tag and 'src' in img_tag.attrs:
                            img_url = img_tag['src']
                            if not img_url.startswith(('http:', 'https:')):
                                img_url = urljoin(self.base_url, img_url)
                            img_urls.append(img_url)
                            print(img_urls)
                            self.root.after(0, lambda: self.status_var.set(f"已提取第{idx}/{total_pages}页图片"))
                        else:
                            self.root.after(0, lambda: self.status_var.set(f"第{idx}页未找到图片,尝试从JS提取"))

                            # 尝试从JavaScript中提取
                            scripts = page_soup.find_all('script')
                            for script in scripts:
                                if script.string:
                                    # 查找img_url变量
                                    match = re.search(r'var\s+img_url\s*=\s*["\']([^"\']+)["\']', script.string)
                                    if match:
                                        img_url = match.group(1)
                                        if not img_url.startswith(('http:', 'https:')):
                                            img_url = urljoin(self.base_url, img_url)
                                        img_urls.append(img_url)
                                        self.root.after(0, lambda: self.status_var.set(
                                            f"从JS提取第{idx}/{total_pages}页图片"))
                                        break

                                    # 尝试其他可能的JS变量名
                                    match = re.search(r'var\s+imgSrc\s*=\s*["\']([^"\']+)["\']', script.string)
                                    if match:
                                        img_url = match.group(1)
                                        if not img_url.startswith(('http:', 'https:')):
                                            img_url = urljoin(self.base_url, img_url)
                                        img_urls.append(img_url)
                                        self.root.after(0, lambda: self.status_var.set(
                                            f"从JS提取第{idx}/{total_pages}页图片"))
                                        break
                    except Exception as e:
                        self.root.after(0, lambda: self.status_var.set(f"提取第{idx}页图片失败: {str(e)}"))
                        continue

            # 去重
            img_urls = list(set(img_urls))

            # 保存下载日志
            with open(os.path.join(save_dir, "download_log.txt"), "w", encoding="utf-8") as f:
                f.write(f"图集标题: {gallery['title']}\n")
                f.write(f"模特: {gallery['model']}\n")
                f.write(f"来源: {gallery['source']}\n")
                f.write(f"总图片数: {len(img_urls)}\n\n")
                f.write("图片URL列表:\n")
                for url in img_urls:
                    f.write(url + "\n")

            # 下载图片
            total = len(img_urls)
            success_count = 0
            failed_count = 0

            for i, url in enumerate(img_urls, 1):
                try:
                    # 显示进度
                    progress = int((i / total) * 100)
                    self.root.after(0, lambda p=progress: self.progress_bar.config(value=p))
                    self.root.after(0, lambda m=f"下载中: {i}/{total}": self.progress_label.config(text=m))

                    # 尝试多个CDN域名
                    base_url = url.split('/upload')[0]
                    path = url.split('/upload')[1]
                    cdn_tried = []

                    for cdn_domain in cdn_domains:
                        cdn_tried.append(cdn_domain)
                        try_url = f"https://{cdn_domain}/upload{path}"
                        response = self.session.get(try_url, timeout=30)
                        if response.status_code == 200 and 'image' in response.headers.get('Content-Type', ''):
                            # 保存图片
                            ext = os.path.splitext(try_url)[1] or '.jpg'
                            filename = f"{i:03d}{ext}"
                            with open(os.path.join(save_dir, filename), 'wb') as f:
                                f.write(response.content)

                            success_count += 1
                            self.root.after(0, lambda: self.status_var.set(f"下载成功: {filename} ({i}/{total})"))
                            break

                    # 如果所有CDN都失败
                    if i == len(img_urls) and success_count == 0:
                        raise Exception(f"所有CDN域名尝试失败: {cdn_tried}")

                    # 添加随机延迟,避免请求过快
                    time.sleep(random.uniform(0.5, 1.5))

                except Exception as e:
                    failed_count += 1
                    print(e)
                    # self.root.after(0, lambda: self.status_var.set(f"下载失败 {i}/{total}: {str(e)}"))

            # 下载完成
            self.root.after(0, lambda: self.progress_bar.config(value=100))
            self.root.after(0, lambda: self.progress_label.config(
                text=f"下载完成: {success_count} 成功, {failed_count} 失败"))
            self.root.after(0, lambda: self.status_var.set(f"图集下载完成: {gallery['title']}"))

            # 显示结果
            result_msg = f"图集下载完成!\n成功: {success_count} 张\n失败: {failed_count} 张\n\n保存路径: {os.path.abspath(save_dir)}"
            self.root.after(0, lambda: messagebox.showinfo("下载完成", result_msg))

        except Exception as e:
            self.root.after(0, lambda: messagebox.showerror("下载错误", f"下载失败: {str(e)}"))
            self.root.after(0, lambda: self.status_var.set("下载失败"))
        finally:
            self.downloading = False
            self.root.after(0, self.update_buttons)

    def sanitize_filename(self, filename):
        """清理文件名,移除不合法字符"""
        invalid_chars = r'[\\/:*?"<>|]'
        return re.sub(invalid_chars, '_', filename)

    def update_buttons(self):
        """更新按钮状态"""
        self.prev_btn.config(state=tk.NORMAL if self.current_page > 1 and not self.downloading else tk.DISABLED)
        self.next_btn.config(
            state=tk.NORMAL if self.current_page < self.max_page and not self.downloading else tk.DISABLED)
        self.page_label.config(text=f"{self.current_page}/{self.max_page}")

        if hasattr(self, 'detail_btn'):
            self.detail_btn.config(
                state=tk.NORMAL if hasattr(self, 'current_selection') and not self.downloading else tk.DISABLED)
            self.download_all_btn.config(
                state=tk.NORMAL if hasattr(self, 'current_selection') and not self.downloading else tk.DISABLED)
            self.download_btn.config(
                state=tk.NORMAL if hasattr(self, 'current_selection') and not self.downloading else tk.DISABLED)

    def prev_page(self):
        """上一页"""
        if self.current_page > 1 and not self.downloading:
            self.current_page -= 1
            self.load_homepage()

    def next_page(self):
        """下一页"""
        if self.current_page < self.max_page and not self.downloading:
            self.current_page += 1
            self.load_homepage()

    def on_category_change(self, event):
        """分类变更事件"""
        self.current_category = self.category_var.get()
        self.current_page = 1
        self.load_homepage()

    def view_details(self):
        """在浏览器中查看详情"""
        if hasattr(self, 'current_selection'):
            webbrowser.open(self.current_selection['link'])

    def open_download_folder(self):
        """打开下载目录"""
        if not os.path.exists("downloads"):
            os.makedirs("downloads")

        # 根据操作系统选择打开方式
        if os.name == 'nt':  # Windows
            os.system('start downloads')
        elif os.name == 'posix':  # macOS/Linux
            os.system('open downloads')

    def show_error(self, message):
        """显示错误消息"""
        messagebox.showerror("错误", message)
        self.status_var.set("就绪")


if __name__ == "__main__":
    root = tk.Tk()
    app = MxdBrowserPro(root)
    root.mainloop()

回复

使用道具 举报

结帖率:81% (26/32)

签到天数: 2 天

发表于 昨天 13:18 | 显示全部楼层   江西省吉安市
main.zip (9.24 KB, 下载次数: 3)
回复

使用道具 举报

结帖率:86% (6/7)

签到天数: 2 天

发表于 昨天 14:39 | 显示全部楼层   广东省河源市
我丢,你这个网站....
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则 致发广告者

发布主题 收藏帖子 返回列表

sitemap| 易语言源码| 易语言教程| 易语言论坛| 易语言模块| 手机版| 广告投放| 精易论坛
拒绝任何人以任何形式在本论坛发表与中华人民共和国法律相抵触的言论,本站内容均为会员发表,并不代表精易立场!
论坛帖子内容仅用于技术交流学习和研究的目的,严禁用于非法目的,否则造成一切后果自负!如帖子内容侵害到你的权益,请联系我们!
防范网络诈骗,远离网络犯罪 违法和不良信息举报QQ: 793400750,邮箱:wp@125.la
网站简介:精易论坛成立于2009年,是一个程序设计学习交流技术论坛,隶属于揭阳市揭东区精易科技有限公司所有。
Powered by Discuz! X3.4 揭阳市揭东区精易科技有限公司 ( 粤ICP备12094385号-1) 粤公网安备 44522102000125 增值电信业务经营许可证 粤B2-20192173

快速回复 返回顶部 返回列表