js怎么编写html里面class,html标签里加class="no-js"有什么作用?

Modernizr是一个JavaScript库,用于检测浏览器对CSS3和HTML5特性的支持,并自动添加或替换HTML元素的类名。no-js类通常在页面加载初期存在,当Modernizr运行后,如果浏览器支持JavaScript,这个类会被移除。通过这种方式,开发者可以针对不同浏览器的支持情况应用不同的CSS样式。了解更多关于Modernizr的使用和配置,可以参考相关文章或访问其官方网站。

制作网站很多时候都会参考别人的代码,今天无意中发现别人的网站html里面加入了一个类名 no-js,即   很好奇,就想着去了解下。

原来这个 no-js 是配合 Modernizr 一起使用的类名(class)

Modernizr 是一个 javascript 库,检查你的游览器是否支持 CSS3 或者 HTML5 的特性而自动添加一些类名(class)到 并 替换掉原来的 .no-js(简单来说,Modernizr 就是一个CSS3/HTML5 的测试器,你需要测试什么,这可以到它的官方网站配置,选择自己需要测试的元素)。

还有让你的游览器支持 HTML5 中的新的标签,例如:, , 和

Modernizr 官方网站:http://modernizr.com/

关于Moderniz 的具体使用方法和解释,本空间也有详细文章解释,请查看《使用Modernizr探测HTML5/CSS3新特性》一文。

<div class="layui-card-body layui-table-body layui-table-main"> <table class="layui-table layui-form" id="demo" lay-filter="test"> <thead> <tr style="height:30px;"> <th lay-data="{width:200, fixed:'left', align:'center', toolbar: '#barDemo'}">操作</th> <th lay-data="{field:'id',sort:true}">No.</th> <th lay-data="{field:'plant',fixed:'left'}">厂别</th> <th lay-data="{field:'kubie'}">库别</th> <th lay-data="{field:'category'}">出货类别</th> <th lay-data="{field:'so_number'}">票号</th> <th lay-data="{field:'pallet_number'}">板号</th> <th lay-data="{field:'liaohao'}">料号</th> <th lay-data="{field:'name'}">品名</th> <th lay-data="{field:'demand'}">需求数量</th> <th lay-data="{field:'prepared_qty'}">备料数量</th> <th lay-data="{field:'creator'}">创建人</th> <th lay-data="{field:'create_time',width:160,fixed:'right'}">上传时间</th> </tr> </thead> <tbody> <?php $i=0;$j=$pagenum;foreach($dataArr as $w){$i++;?> <tr data-id="<?= $w['id'] ?>" data-category="<?= htmlspecialchars($w['category']) ?>" data-prepared-qty="<?= $w['prepared_qty'] ?>" data-demand="<?= $w['demand'] ?>" data-pallet="<?= htmlspecialchars($w['pallet_number']) ?>"> <td> <div style="display: inline-flex; gap: 6px; align-items: center; justify-content: center;"> <button class="layui-btn layui-btn-xs edit-btn">编辑</button> <button class="layui-btn layui-btn-xs layui-btn-normal print-btn">打印</button> </div> </td> <td><?= $i ?></td> <td><?= htmlspecialchars($w['plant']) ?></td> <td><?= htmlspecialchars($w['kubie']) ?></td> <td><?= htmlspecialchars($w['category']) ?></td> <td><?= htmlspecialchars($w['so_number']) ?></td> <td><?= htmlspecialchars($w['pallet_number']) ?></td> <td><?= htmlspecialchars($w['material']) ?></td> <td><?= htmlspecialchars($w['name']) ?></td> <td><?= htmlspecialchars($w['demand']) ?></td> <td><?= htmlspecialchars($w['prepared_qty']) ?></td> <td><?= htmlspecialchars($w['creator']) ?></td> <td><?= htmlspecialchars($w['create_time']) ?></td> </tr> <?php $j++;} ?> </tbody> </table> </div>$('.print-btn').on('click', function () { // 获取当前按钮所在行的板号(通过 data-pallet 属性或遍历 td) var palletNumber = $(this).data('pallet'); // 创建弹窗内容容器 var contentHtml = '<div style="padding: 20px; text-align: center;"><p>一维码:</p><svg id="barcode" style="width: 200px; height: 60px; margin: 0 auto;"></svg><p style="margin-top: 10px;">板号:<strong>${palletNumber}</strong></p></div>'; // 使用 layer 弹窗 layer.open({ type: 1, title: '打印预览', area: ['280px', '180px'], content: contentHtml, success: function (layero, index) { // 弹窗打开成功后生成条形码 try { JsBarcode("#barcode", palletNumber, { format: "CODE128", // 一维码格式(常用 CODE128 支持数字字母) displayValue: false, // 不在条形码下方显示文字(我们自己显示) width: 2, height: 50 }); } catch (e) { console.error("条形码生成失败:", e); $("#barcode", layero).html("条码生成失败"); } } }); });为什么打印按钮点了没反应
最新发布
11-26
<template> <div class="sec-container"> <van-popup v-model:show="showDetailModal" :style="{ width: showType === 'qcOrder'?'30%':'100%', height: '100%', overflow: 'auto' }" position="right" round closeable @close="handleDetailClose" > <van-form ref="addCommentsFormRef" @submit="handleSave"> <!-- 检验单信息 --> <van-cell-group title="检验单信息" v-if="showType === 'qcOrder'"> <!-- <van-field--> <!-- v-model="saveData.applyDate"--> <!-- is-link--> <!-- readonly--> <!-- clearable--> <!-- @click="handleDate"--> <!-- placeholder="请选择提出时间"--> <!-- />--> <van-field v-model="saveData.applyDate" is-link readonly label="提出时间" placeholder="点击选择日期" @click="showDatePicker = true" /> <van-popup v-model:show="showDatePicker" round position="bottom"> <van-date-picker v-model="saveData.applyDate" @confirm="onConfirm" @cancel="showDatePicker = false" /> </van-popup> </van-cell-group> <!-- 编辑/新增时的表单 --> <van-cell-group title="检验单信息" v-if="showType === 'show' || showType === 'result'"> <van-field name="radio" label="检验状态" v-if="showType === 'result'"> <template #input> <van-radio-group v-model="saveData.status" direction="horizontal"> <van-radio name="0">新建</van-radio> <van-radio name="1">检验中</van-radio> <van-radio name="2">合格</van-radio> <van-radio name="-1">不合格</van-radio> </van-radio-group> </template> </van-field> <van-field name="projectNo" label="项目"> <template #input> {{saveData.projectNo}} </template> </van-field> <van-field name="inspClass" label="检验大类" > <template #input> {{saveData.inspClassStr}} </template> </van-field> <van-field name="inspType" label="检验类型" > <template #input> {{saveData.inspTypeName}} </template> </van-field> <van-field name="orgNo" label="基地" > <template #input> {{saveData.orgName}} </template> </van-field> <van-field name="workzone" label="作业区" > <template #input> {{saveData.workzoneName}} </template> </van-field> <van-field name="inspLoc" label="检验地点" > <template #input> {{saveData.inspLoc}} </template> </van-field> <van-field name="mainUserNo" label="负责人" > <template #input> {{saveData.mainUserName}} </template> </van-field> <van-field name="tel" label="联系方式"> <template #input> {{saveData.tel}} </template> </van-field> </van-cell-group> <!-- 管路信息 --> <van-cell-group title="管路信息" v-if="showType != 'qcOrder'"> <!-- 简化显示 --> <div class="search-btn" v-if="showType === 'result'"> <van-button type="primary" @click="changeStatus(1,'pipe')" style="margin-right:10px">合格</van-button> <van-button type="primary" @click="changeStatus(-1,'pipe')" style="margin-right:10px">不合格 </van-button> <van-button type="primary" @click="changeStatus(2,'pipe')" style="margin-right:10px">取消 </van-button> </div> <a-table sticky v-if="showType == 'show'" :scroll="{ x: '100%' }" :columns="pipeTable.columns" :dataSource="pipeTable.dataSource" bordered size="middle" :loading="loading" :pagination="false" rowKey="id" ref="pipeTableRef" > </a-table> <a-table sticky v-if="showType == 'result'" :scroll="{ x: '100%' }" :columns="pipeTable.resColumns" :dataSource="pipeTable.dataSource" bordered size="middle" :loading="loading" :pagination="false" rowKey="id" ref="pipeTableRef" :row-selection="{ selectedRowKeys: pipeTable.selectedRowKeys, onChange: onSelectChange }" > </a-table> </van-cell-group> <!-- 焊缝信息 --> <van-cell-group title="焊缝信息" v-if="showType != 'qcOrder'"> <div class="search-btn" v-if="showType === 'result'"> <van-button type="primary" @click="changeStatus(1,'weld')" style="margin-right:10px">合格</van-button> <van-button type="primary" @click="changeStatus(-1,'weld')" style="margin-right:10px">不合格 </van-button> <van-button type="primary" @click="changeStatus(2,'weld')" style="margin-right:10px">取消 </van-button> </div> <a-table sticky v-if="showType == 'show'" :scroll="{ x: '100%' }" :columns="weldTable.columns" :dataSource="weldTable.dataSource" bordered size="middle" :loading="loading" :pagination="false" rowKey="id" ref="weldTableRef" > </a-table> <a-table sticky v-if="showType == 'result'" :scroll="{ x: '100%' }" :columns="weldTable.resColumns" :dataSource="weldTable.dataSource" bordered size="middle" :loading="loading" :pagination="false" rowKey="id" ref="weldTableRef" :row-selection="{ selectedRowKeys: weldTable.selectedRowKeys, onChange: onWeldSelectChange }" > </a-table> </van-cell-group> <!-- 提交按钮 --> <div style="margin: 16px;"> <van-button round block type="default" @click="handleDetailClose">取消</van-button> <van-button round block type="primary" @click = 'handleSave' native-type="submit" v-if=" showType === 'result' || showType === 'qcOrder'">提交</van-button> </div> </van-form> </van-popup> <div class="top-content"> <div class="project-content"> <div class=""></div> </div> <div class="btn-content"> <div class="btn-right" @click="openSearchDialog"> <SearchOutlined style="font-size: 20px; color: #004688" /> <p class="speNorm">搜索</p> </div> </div> </div> <div class="bot-content" @scroll="handleScroll"> <a-table sticky :scroll="{ x: '100%' }" :columns="columns" :dataSource="data" bordered size="middle" :loading="loading" :pagination="false" rowKey="id" > <template #bodyCell="{ column, record }"> <template v-if="column.key === 'action'"> <a-button type="link" @click="showData(record,'show')" v-resource="[{ url: '/service-piping/cp/insp/order', method: 'POST' }]">查看</a-button> <a-button type="link" @click="showData(record,'result')" v-resource="[{ url: '/service-piping/cp/insp/order', method: 'POST' }]">结果维护</a-button> <a-button type="link" @click="showData(record,'qcOrder')" v-if="record.applyFlag!='Y'" >申请检验</a-button> </template> </template> </a-table> </div> <van-popup v-model:show="searchOpen" position="right" :style="{ width: popupWidth, height: '100%', overflow: 'hidden' }" round closeable @close="handleDetailClose" > <div class="search-content"> <div class="search-top"> <div class="search-project-back" v-if="projectType" @click="handleBack"> <LeftOutlined style="font-size: 15px; color: #004688; margin-top: 3px" />返回 </div> <div class="search-titles"> <span>{{ !projectType ? '搜索' : '搜索项目' }}</span> </div> </div> <div class="search-state"> <van-cell-group inset> <van-field v-model="searchState.projectNo" clearable label="项目" readonly @click="handleProject" /> <van-field v-model="searchState.orderNo" clearable label="检验单号" /> <van-field v-model="searchState.inspClass" clearable @click="handleInspClass" label="检验大类" is-link readonly /> </van-cell-group> <van-popup v-model:show="showPicker" round position="bottom"> <van-picker :columns="inspColumns" @cancel="showPicker = false" @confirm="onInspClassConfirm" /> </van-popup> <project-panel ref="projectpanelRef" @get="getProj"></project-panel> </div> <div class="search-btn"> <van-button type="warning" color="#9A9A9A" @click="onReset">重置</van-button> <van-button type="primary" @click="onSearch">查询 </van-button> </div> </div> </van-popup> </div> </template> <script setup> import { ref, onMounted, onUnmounted, onActivated, computed, watch, createVNode, reactive } from 'vue' import { SearchOutlined, LeftOutlined, ExclamationCircleOutlined } from '@ant-design/icons-vue' import * as serve from '@/api/cp/pipeInspection' import ProjectPanel from '@/components/ProjectPanel.vue' import dayjs from 'dayjs' import { Buffer } from 'buffer' import { useRouter } from 'vue-router' import localforage from 'localforage' import { useHomePage } from '@/stores/homePage' import { Modal } from 'ant-design-vue' import {getBuildBases} from "@/api/common/index.js"; import {showNotify} from "vant"; import { cloneDeep } from 'lodash-es' const showDatePicker = ref(false); const onSelectChange = (keys, rows) => { pipeTable.selectedRowKeys = keys pipeTable.selectedRows = rows } const onWeldSelectChange = (keys, rows) => { weldTable.selectedRowKeys = keys weldTable.selectedRows = rows } const workZoneNos = ref([]) const statusOptions = ref([ { value: -1, text: "不合格" }, { value: 0, text: "新建" }, { value: 1, text: "合格" }, { value: 2, text: "取消" } ]) // const inspClassOptions = [ // { text: '焊前检验', value: 'HQ' }, // { text: '焊后检验', value: 'HH' }, // { text: '压力检验', value: 'PT' }, // { text: '完工检验', value: 'HP' }, // { text: '其他检验', value: 'JY' }, // ] const currProjectNo =ref(null) const selectInsp = ref([]) const weldTable = reactive({ toolbar: { // buttons: [{ code: "addCar", name: "添加物料" }] }, resToolbar: { buttons: [{ code: "qualifiedWeld", name: "合格" },{ code: "unqualifiedWeld", name: "不合格" },{ code: "cancelWeld", name: "取消" }] }, selectedRowKeys: [], selectedRows: [], columns: [ { title: "管号", dataIndex: "pipeNo", width: 120, }, { title: "焊缝号", dataIndex: "weldNo", width: 150, }, { title: "焊缝长度", dataIndex: "length", width: 150, }, { title: "焊接日期", type: "date", dataIndex: "weldDate", width: 150, }, // { // title: "操作", // key: "action", // align: "center", // width: 90, // sorter: false, // scopedSlots: { customRender: "action" }, // fixed: "right", // formInvisible: true // } ], resColumns: [ { title: "项目", dataIndex: "projectNo", width: 90, type: "project", }, { title: "管号", dataIndex: "pipeNo", width: 120, }, { title: "版本号", dataIndex: "pipeVersion", width: 120, }, { title: "焊缝号", dataIndex: "weldNo", width: 150, }, { title: "检验结果", dataIndex: "status", type: "select", width: 80, customRender: function (text) { if (text.record.status) { return stateColumns.value.find( (item) => item.value == text.record.status ).text } else { return '新建' } } }, { title: "小票删除", dataIndex: "pipeDelFlag", width: 80, }, { title: "小票暂停", dataIndex: "pipePauseFlag", width: 80, }, { title: "小票最新版", dataIndex: "pipeTopFlag", width: 80, }, { title: "焊缝删除", dataIndex: "delFlag", width: 80, }, { title: "操作", key: "action", align: "center", width: 200, sorter: false, scopedSlots: { customRender: "action" }, fixed: "right", formInvisible: true } ], dataSource: [] }) const showType = ref('') window.Buffer = Buffer const router = useRouter() const homePage = useHomePage() const saveData = ref({}) const stateColumns = ref( [{ value: 0, text: "新建" }, { value: -1, text: "不合格" }, { value: 1, text: "检验中" }, { value: 2, text: "合格" },] ) const showDetailModal = ref(false) const inspColumns = ref([ { value: 'HQ', text: "焊前检验" }, { value: 'HH', text: "焊后检验" }, { value: 'PT', text: "压力检验" }, { value: 'HP', text: "完工检验" }, { value: 'JY', text: "其他检验" }, ]) const showPicker = ref(false) const buildBaseList = ref([]) const data = ref([]) const loading = ref(false) const dataCount = ref(0) const searchState = ref({ size: 20, page: 0, projectNo: undefined, inspClass: undefined, orderNo: undefined, }) const searchOpen = ref(false) const projectpanelRef = ref(null) const projectType = ref(false) const popupWidth = ref('30%') const isLast = ref(false) const statusList = ref([ { text: '新建', value: 0 }, { text: '进行中', value: 1 }, { text: '已完成', value: 2 } ]) const pipeTable = reactive({ toolbar: { buttons: [{ code: "addPipe", name: "添加物料" }] }, resToolbar: { buttons: [{ code: "qualifiedPipe", name: "合格" },{ code: "unqualifiedPipe", name: "不合格" },{ code: "cancelPipe", name: "取消" }] }, selectedRowKeys: [], selectedRows: [], columns: [ { title: "图号", dataIndex: "drawNo", width: 120, }, { title: "作业对象", dataIndex: "block", width: 150, }, { title: "托盘号", dataIndex: "instPalletNo", width: 150, }, { title: "管号", dataIndex: "pipeNo", width: 120, }, { title: "页号", dataIndex: "pageNo", width: 80, }, { title: "暂停", dataIndex: "isPause", width: 80, }, { title: "删除", dataIndex: "isDelete", width: 80, }, { title: "最新版", dataIndex: "isTopVersion", width: 80, }, { title: "装配承包商", dataIndex: "assyCoopName", width: 150, }, { title: "焊接承包商", dataIndex: "weldingCoopName", width: 150, }, { title: "焊前检验单", dataIndex: "weldingPreOrderNo", width: 150, }, { title: "焊后检验单", dataIndex: "weldingPostOrderNo", width: 150, }, { title: "压力检验单", dataIndex: "ptOrderNo", width: 150, }, { title: "完工/预制放行检验单", dataIndex: "preFinishOrderNo", width: 150, }, { title: "PMI检验单", dataIndex: "pmiOrderNo", width: 150, }, { title: "焊后热处理检验单", dataIndex: "pwhtOrderNo", width: 150, }, { title: "硬度检验单", dataIndex: "hardnessOrderNo", width: 150, }, { title: "铁素体检验单", dataIndex: "ferriteOrderNo", width: 150, }, // { // title: "操作", // key: "action", // align: "center", // sorter: false, // scopedSlots: { customRender: "action" }, // fixed: "right", // formInvisible: true // } ], resColumns: [ { title: "项目", dataIndex: "projectNo", width: 90, type: "project", }, { title: "管号", dataIndex: "pipeNo", width: 120, }, { title: "版本号", dataIndex: "pipeVersion", width: 80, }, { title: "删除", dataIndex: "isDelete", width: 80, }, { title: "暂停", dataIndex: "isPause", width: 80, }, { title: "最新版", dataIndex: "isTopVersion", width: 80, }, { title: "检验结果", dataIndex: "status", type: "select", width: 80, customRender: function (text) { if (text.record.status) { return statusOptions.value.find( (item) => item.value == text.record.status ).text } else { return '新建' } } }, { title: "操作", key: "action", align: "center", width: 200, sorter: false, scopedSlots: { customRender: "action" }, fixed: "right", formInvisible: true } ], dataSource: [] }) const columns = computed(() => { return [ { title: '项目号', dataIndex: 'projectNo', width: 120 }, { title: "基地", dataIndex: "orgNo", condition: true, width: 90, customRender: function (text) { if (text.record.orgNo) return buildBaseList.value.find( (item) => item.value == text.record.orgNo ).text }, }, { title: "检验单号", dataIndex: "orderNo", condition: true, width: 150 }, { dataIndex: "workzoneName", title: "作业区", width: 150, }, { title: "QC单号", dataIndex: "qcOrderNo", condition: true, width: 150 }, { title: "申请检验时间", dataIndex: "createDate", width: 150, type: "datetime", align: "center", customRender: function (text) { if (text.record.createDate) return dayjs(text.record.createDate).format('YYYY-MM-DD HH:mm:ss') }, }, { title: "检验大类", dataIndex: "inspClass", type: "select", width: 100, condition: true, customRender: function (text) { if (text.record.inspClass) return inspColumns.value.find( (item) => item.value == text.record.inspClass ).text }, }, { title: "检验类型", dataIndex: "inspTypeName", width: 150, }, { title: "创建时间", dataIndex: "createDate", width: 150, type: "datetime", customRender: function (text) { if (text.record.createDate) return dayjs(text.record.createDate).format('YYYY-MM-DD HH:mm:ss') }, }, { title: "创建人", dataIndex: "createUserId", width: 120, type: "employeeDescription", options: { fieldNames: { label: "createUserName", value: "createUserId" } } }, { title: "管数量", dataIndex: "pipeNum", width: 80 }, { title: "状态", dataIndex: "status", type: "select", width: 80, condition: true, customRender: function (text) { if (text.record.status) { return statusOptions.value.find( (item) => item.value == text.record.status ).text } else { return '新建' } } }, { title: '操作', dataIndex: 'action', width: 140, key: 'action' } ] }) const loadData = (type) => { loading.value = true serve.getOrderList(searchState.value).then(async (res) => { for (let item of res.content) { item.targetStorageDate = item.targetStorageDate ? dayjs(item.targetStorageDate).format('YYYY-MM-DD') : '' item.isDownload = false let value = await localforage.getItem(item.id.toString()) if (value instanceof Blob && value.size > 0 && value.type === 'application/pdf') { item.isDownload = true } } if (type == 'scroll') { data.value.push(...res.content) } else { data.value = res.content } dataCount.value = res.totalElements || 0 isLast.value = res.last loading.value = false }) } const handleScroll = (e) => { const element = e.target if (element.scrollTop + element.clientHeight >= element.scrollHeight) { if (!isLast.value) { searchState.value.page++ loadData('scroll') } } } const openSearchDialog = () => { searchOpen.value = true } const handleProject = () => { projectType.value = true projectpanelRef.value.open = true projectpanelRef.value.init() } const handleBack = () => { projectType.value = false projectpanelRef.value.open = false } const handleClose = () => { projectType.value = false projectpanelRef.value.open = false } const handleDetailClose = () => { showDetailModal.value = false } const handleResize = () => { if (window.innerWidth < 1000) { popupWidth.value = '60%' } else { popupWidth.value = '30%' } } const getProj = (val) => { searchState.value.projectNo = val.projId searchState.value.projNo = val.projNo handleClose() } const onSearch = () => { searchState.value.page = 0 loadData() searchOpen.value = false } const onReset = () => { searchState.value.page = 0 searchState.value.projectNo = undefined searchState.value.projNo = undefined searchState.value.inspClass = undefined searchState.value.orderNo = undefined } const handleBeforeUnload = () => { if (!homePage.isOnline) { Modal.confirm({ title: '浏览器当前处于离线状态,是否继续?', icon: createVNode(ExclamationCircleOutlined), content: createVNode( 'div', { style: 'color:red;' }, '一旦刷新,将无法继续使用' ), onOk() { console.log('OK') }, onCancel() { console.log('Cancel') } }) } } watch( () => homePage.isOnline, async (newVal) => { if (!newVal) { try { const listData = await localforage.getItem('downloadList') if (listData) { const list = JSON.parse(listData) list.forEach((item) => { item.isDownload = true }) data.value = list } } catch (error) { console.error('Error parsing list data:', error) } } else { onReset() loadData() } } ) const onInspClassConfirm = ({ selectedOptions }) => { showPicker.value = false searchState.value.inspClass = selectedOptions[0].value } const handleInspClass = () => { showPicker.value = true } onMounted(() => { getBuildBase() serve.getWorkZone({majors:'管路'}).then((res) => { workZoneNos.value = res.status == 200 ? res.data.map((item) => { return { label: item.name, value: item.code, } }) : []; }) if (homePage.isOnline) { loadData() } handleResize() window.addEventListener('resize', handleResize) window.addEventListener('beforeunload', handleBeforeUnload) }) onUnmounted(() => { window.removeEventListener('resize', handleResize) window.removeEventListener('beforeunload', handleBeforeUnload) }) onActivated(async () => { if (!homePage.isOnline) { try { const listData = await localforage.getItem('downloadList') if (listData) { const list = JSON.parse(listData) list.forEach((item) => { item.isDownload = true }) data.value = list } } catch (error) { console.error('Error parsing list data:', error) } } }) const getBuildBase = () => { getBuildBases().then((res) => { buildBaseList.value = res.map((item) => ({ value: item.orgNo, text: item.name })) }) } const showData = (record,showTypeStr) => { serve.getOrderList({id:record.id}).then((res) => { console.log('res.data',res) console.log('res.data',res.data) console.log('res.data.content',res.content) saveData.value = res.content[0] saveData.value.mainUserNo = { label: saveData.value.mainUserName, value: saveData.value.mainUserNo } saveData.value.status = saveData.value.status+'' pipeTable.dataSource = res.content[0].pipeList console.log('pipeTable.dataSource',pipeTable.dataSource) weldTable.dataSource = res.content[0].weldList currProjectNo.value = res.content[0].projectNo showDetailModal.value = true showType.value = showTypeStr saveData.value.inspClassStr = inspColumns.value.find( (item) => item.value == saveData.value.inspClass ).text saveData.value.orgName = buildBaseList.value.find( (item) => item.value == saveData.value.orgNo ).text serve.getInsp({ projName: res.content[0].projectNo, professionNo: "CP" }).then((res) => { selectInsp.value = res.map((item) => { return { label: item.MSVALUE+"", value: item.MSKEY+"", } }) }) }) } const onConfirm = ({ selectedValues }) => { saveData.applyDate = selectedValues.join('-'); console.log('saveData.applyDate',saveData.applyDate) console.log('selectedValues',selectedValues) showDatePicker.value = false; }; const changeStatus = (stateNum,type)=>{ console.log('pipeTable',pipeTable.selectedRows) console.log('weldTable',weldTable.selectedRows) if(type === 'pipe' ){ if(pipeTable.selectedRows.length === 0 ){ showNotify({ type: 'danger', message: '请选择一条数据' }) return } pipeTable.dataSource = pipeTable.dataSource.map(item => { const selectedItem = pipeTable.selectedRows.find(row => row.id === item.id); return selectedItem ? { ...item, status: stateNum } : item; }); } if(type === 'weld'){ if(weldTable.selectedRows.length === 0 ){ showNotify({ type: 'danger', message: '请选择一条数据' }) return } weldTable.dataSource = weldTable.dataSource.map(item => { const selectedItem = weldTable.selectedRows.find(row => row.id === item.id); return selectedItem ? { ...item, status: stateNum } : item; }); } console.log('pipeTable.dataSource',pipeTable.dataSource) console.log('weldTable.dataSource',weldTable.dataSource) } const handleSave = () => { let param = cloneDeep(saveData.value) if(showType.value == 'result'){ param.editType = 1 param.id = saveData.value.id } else { param.getEditType = 0 if('edit' == showType.value){ param.id = saveData.value.id } } if (param.mainUserNo && param.mainUserNo.value) { param.mainUserName = param.mainUserNo.label param.mainUserNo = param.mainUserNo.value } else { param.mainUserNo = '' } param.pipeList = pipeTable.dataSource param.weldList = weldTable.dataSource serve.saveOrderList(param).then(() => { showNotify({ type: 'success', message: '操作成功' }) // if(type.value != 'result'){ showDetailModal.value = false // } }) } </script> <style lang="scss" scoped> :deep .urgentFlag-active { background: #ffb656; } :deep .abnormalFlag-active { background: #fb7171; } :deep .anticon-search { line-height: 0 !important; } </style> <style lang="scss" scoped> @import '@/styles/common.scss'; .search-content { width: 100%; overflow: hidden; // height: 100%; .search-top { width: 100%; height: 50px; margin-top: 20px; display: flex; justify-content: center; align-content: center; .search-project-back { font-size: 14px; display: flex; align-content: center; position: absolute; left: 10px; color: #004688; } .search-titles { height: 100%; display: flex; justify-content: center; align-content: center; color: #004688; span { font-size: 15px; margin-left: 5px; } } } .search-state { width: 100%; height: calc(100% - 45px - 10px); position: absolute; bottom: 0; overflow: hidden; overflow-y: scroll; } } .action-buttons { display: flex; align-items: center; justify-content: space-around; } </style> 点击申请检验后点击提出时间没有弹出日期选择框
09-17
import time import json import os import argparse import traceback import copy from datetime import datetime, timedelta from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.common.exceptions import ( TimeoutException, ElementClickInterceptedException, ElementNotInteractableException, NoSuchElementException, WebDriverException ) import undetected_chromedriver as uc class MIACrawler: def __init__(self, download_dir=None, headless=False, use_profile=True, pause_on_navigation=False): """ 初始化Medical Image Analysis爬虫 Args: download_dir: 下载目录 headless: 是否使用无头模式 use_profile: 是否使用配置文件 pause_on_navigation: 导航时是否暂停 """ # 默认参数设置 self.default_save_dir = download_dir or r"d:\期刊网站爬虫\downloads_mia" self.journal_url = "https://www.sciencedirect.com/search/entry" self.cookie_file = r"d:\期刊网站爬虫\cookies\cookies_sciencedirect_mia.json" self.headless = headless self.use_profile = use_profile self.pause_on_navigation = pause_on_navigation # 确保下载目录存在 os.makedirs(self.default_save_dir, exist_ok=True) # 初始化实例变量 self.driver = None self.wait = None self.download_dir = self.default_save_dir def pause_if_enabled(self): """如果启用了暂停功能,则暂停执行""" if self.pause_on_navigation: try: print("\n[PAUSE] 按Enter键继续...") input() except KeyboardInterrupt: print("\n用户中断操作") raise def setup_driver(self): """设置Chrome驱动""" try: print("正在初始化Chrome浏览器驱动...") # 创建Chrome选项并配置下载路径 chrome_options = uc.ChromeOptions() prefs = { "download.default_directory": self.download_dir, "download.prompt_for_download": False, "download.directory_upgrade": True, "safebrowsing.enabled": True } chrome_options.add_experimental_option("prefs", prefs) # 添加更多配置以提高稳定性 chrome_options.add_argument("--disable-gpu") chrome_options.add_argument("--no-sandbox") chrome_options.add_argument("--disable-dev-shm-usage") chrome_options.add_argument("--disable-extensions") chrome_options.add_argument("--start-maximized") if self.headless: chrome_options.add_argument("--headless") chrome_options.add_argument("--window-size=1920,1080") # 使用本地Chrome浏览器,避免网络问题 try: # 尝试使用本地Chrome self.driver = uc.Chrome(options=chrome_options, use_subprocess=True) print(f"浏览器驱动初始化成功!下载目录设置为: {self.download_dir}") except Exception as e: print(f"首次初始化失败,尝试备用配置: {e}") # 尝试使用更多备用配置 try: # 使用本地Chrome安装路径 chrome_path = "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe" if os.path.exists(chrome_path): chrome_options.binary_location = chrome_path print(f"使用本地Chrome路径: {chrome_path}") # 禁用自动更新检查 self.driver = uc.Chrome( options=chrome_options, use_subprocess=True, version_main=None # 不指定Chrome版本,使用本地版本 ) print(f"备用配置初始化成功!下载目录设置为: {self.download_dir}") except Exception as e: print(f"备用配置也失败: {e}") raise # 设置显式等待 self.wait = WebDriverWait(self.driver, 30) return True except Exception as e: print(f"设置驱动失败: {e}") return False def take_screenshot(self, filename): """截取当前页面截图""" try: # 确保截图目录存在 screenshot_dir = os.path.join(os.path.dirname(self.default_save_dir), "screenshots_mia") os.makedirs(screenshot_dir, exist_ok=True) # 生成截图文件名 timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") screenshot_path = os.path.join(screenshot_dir, f"{filename}_{timestamp}.png") # 截取并保存截图 self.driver.save_screenshot(screenshot_path) print(f"✓ 截图已保存: {screenshot_path}") return screenshot_path except Exception as e: print(f"✗ 截图失败: {e}") return None def save_page_source(self, filename): """保存当前页面源码""" try: # 确保源码目录存在 source_dir = os.path.join(os.path.dirname(self.default_save_dir), "page_sources_mia") os.makedirs(source_dir, exist_ok=True) # 生成源码文件名 timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") source_path = os.path.join(source_dir, f"{filename}_{timestamp}.html") # 保存页面源码 with open(source_path, 'w', encoding='utf-8') as f: f.write(self.driver.page_source) print(f"✓ 页面源码已保存: {source_path}") return source_path except Exception as e: print(f"✗ 保存页面源码失败: {e}") return None def save_cookies(self): """保存浏览器cookies""" try: # 确保目录存在 os.makedirs(os.path.dirname(self.cookie_file), exist_ok=True) # 获取当前cookies cookies = self.driver.get_cookies() print(f"正在保存cookies,共{len(cookies)}个...") # 保存到文件 with open(self.cookie_file, 'w', encoding='utf-8') as f: json.dump(cookies, f, ensure_ascii=False, indent=2) print(f"✓ Cookies已保存到: {self.cookie_file}") return True except Exception as e: print(f"✗ 保存cookies时出错: {e}") return False def load_cookies(self): """加载cookies到浏览器""" if not os.path.exists(self.cookie_file): print(f"⚠️ Cookie文件不存在: {self.cookie_file}") return False try: with open(self.cookie_file, 'r', encoding='utf-8') as f: cookies = json.load(f) print(f"正在加载cookies,共{len(cookies)}个...") for cookie in cookies: # 删除可能导致问题的属性 if 'expirationDate' in cookie: del cookie['expirationDate'] if 'storeId' in cookie: del cookie['storeId'] if 'sameSite' in cookie and cookie['sameSite'] is None: cookie['sameSite'] = 'Lax' try: self.driver.add_cookie(cookie) except Exception as e: print(f"⚠️ 添加cookie失败: {cookie.get('name')} - {e}") print("✓ Cookies加载完成") return True except Exception as e: print(f"✗ 加载cookies时出错: {e}") return False def navigate_to_search_page(self): """导航到搜索页面""" try: print(f"正在访问 {self.journal_url}") self.driver.get(self.journal_url) time.sleep(5) # 给页面一些加载时间 # 检查页面标题以确认访问成功 page_title = self.driver.title print(f"✓ 已成功访问搜索页面") print(f"ℹ️ 页面标题: {page_title}") return True except Exception as e: print(f"✗ 导航到搜索页面失败: {e}") self.take_screenshot("navigate_error") return False def setup_search_criteria(self): """设置搜索条件""" try: print("\n正在设置搜索条件...") # 搜索框: //*[@id="qs"] search_box = self.wait.until(EC.presence_of_element_located( (By.XPATH, "//*[@id='qs']") )) search_box.clear() search_query = '(ultrasound OR ultrasonic) AND (AI OR "artificial intelligence" OR "deep learning" OR "neural network")' search_box.send_keys(search_query) print(f"✓ 输入关键词搜索: {search_query}") # 期刊搜索栏: //*[@id="pub"] pub_box = self.wait.until(EC.presence_of_element_located( (By.XPATH, "//*[@id='pub']") )) pub_box.clear() pub_query = "neurocomputing" pub_box.send_keys(pub_query) print(f"✓ 输入期刊: {pub_query}") # 年份选择框: //*[@id="date"] year_box = self.wait.until(EC.presence_of_element_located( (By.XPATH, "//*[@id='date']") )) year_box.clear() current_year = datetime.now().year year_box.send_keys(str(current_year)) print(f"✓ 输入年份: {current_year}") # 点击搜索按钮 search_button = self.wait.until(EC.element_to_be_clickable( (By.XPATH, "//*[@id='search-advanced-form']/div/div/div[4]/div/div[2]/button/span/span") )) search_button.click() print("✓ 点击搜索按钮") # 等待搜索结果加载完成 print("ℹ️ 等待搜索结果加载完成...") # 改为直接暂停8秒 time.sleep(8) print("✓ 等待8秒完成") self.pause_if_enabled() # 搜索结果加载后暂停 return True except Exception as e: print(f"✗ 设置搜索条件失败: {str(e)}") self.save_page_source("search_criteria_error") self.take_screenshot("search_criteria_error") return False def sort_by_date(self): """按日期排序搜索结果""" try: print("\n⚡ 正在按日期排序搜索结果 - 开始监视...") # 先显式等待排序按钮元素出现 print("ℹ️ 显式等待排序按钮元素出现...") self.wait.until(EC.presence_of_element_located( (By.XPATH, "//*[@id='srp-sorting-options']/div/a/span") )) print("✓ 排序按钮元素已出现") # 直接查找元素进行预检 try: pre_check_element = self.driver.find_element(By.XPATH, "//*[@id='srp-sorting-options']/div/a/span") print(f"✓ 预检:排序按钮元素存在") print(f"ℹ️ 预检元素状态 - 可见性: {pre_check_element.is_displayed()}, 可点击性: {pre_check_element.is_enabled()}") # 获取元素详细信息 button_text = pre_check_element.text.strip() or "无文本" button_class = pre_check_element.get_attribute('class') or "无class属性" button_href = pre_check_element.get_attribute('href') or "无href属性" print(f"ℹ️ 排序按钮详细信息 - 文本: '{button_text}', Class: {button_class}, Href: {button_href}") except Exception as pre_check_e: print(f"⚠️ 预检失败,可能元素暂时不可见: {str(pre_check_e)}") # 使用显式等待获取可点击的按钮 print("ℹ️ 使用显式等待精确查找可点击的排序按钮...") start_wait_time = time.time() sort_button = self.wait.until(EC.element_to_be_clickable( (By.XPATH, "//*[@id='srp-sorting-options']/div/a/span") )) wait_duration = time.time() - start_wait_time print(f"✅ 成功定位可点击的排序按钮,等待用时: {wait_duration:.3f}秒") # 点击前再次检查元素状态 print(f"ℹ️ 点击前最终检查 - 可见性: {sort_button.is_displayed()}, 可点击性: {sort_button.is_enabled()}") # 记录点击前页面状态作为参考 before_url = self.driver.current_url print(f"ℹ️ 点击前URL: {before_url}") # 执行点击操作 print("⚡ 执行排序按钮点击操作...") click_start_time = time.time() try: sort_button.click() click_duration = time.time() - click_start_time print(f"✅ 排序按钮点击操作执行完成,用时: {click_duration:.3f}秒") except ElementClickInterceptedException: print(f"✗ 排序按钮点击被拦截,元素可能被覆盖") # 尝试使用JavaScript点击作为备选方案 print(f"⚡ 尝试使用JavaScript点击排序按钮") self.driver.execute_script("arguments[0].click();", sort_button) print(f"✅ JavaScript点击排序按钮执行完成") except Exception as click_e: print(f"✗ 排序按钮点击失败: {str(click_e)}") print(f"ℹ️ 错误类型: {type(click_e).__name__}") # 尝试获取更多诊断信息 try: page_source = self.driver.page_source[:500] # 只获取部分页面源码用于诊断 print(f"ℹ️ 页面源码片段: {page_source}") except: print("ℹ️ 无法获取页面源码用于诊断") raise # 使用显式条件等待排序操作完成 print("ℹ️ 等待排序操作完成...") try: # 等待页面状态变化(URL变化或新内容加载) if after_url == before_url: # URL未变化,等待内容更新(例如等待排序指示器消失或新的结果出现) print("ℹ️ URL未变化,等待内容更新...") # 等待排序选项列表可能收起 try: self.wait.until_not(EC.visibility_of_element_located( (By.XPATH, "//*[@id='srp-sorting-options']//option") )) print("✓ 排序选项列表已收起") except Exception as dropdown_e: print(f"ℹ️ 排序选项列表状态检查异常: {str(dropdown_e)}") else: # URL已变化,等待新页面加载完成 print("ℹ️ URL已变化,等待页面加载完成...") self.wait.until(EC.presence_of_element_located( (By.XPATH, "//div[contains(@class, 'result-list') or @id='search-results-list']") )) print("✓ 新页面内容已加载") # 最终验证 - 检查排序后的文章列表是否可见 articles = self.wait.until(EC.presence_of_all_elements_located( (By.XPATH, "//article[contains(@class, 'result-item')] | //li[contains(@class, 'search-result')]") )) print(f"✓ 排序完成,找到 {len(articles)} 篇文章") except Exception as sort_wait_e: print(f"⚠️ 显式等待排序完成失败,使用备用等待: {str(sort_wait_e)}") # 如果显式等待失败,使用较短的备用等待 time.sleep(3) # 验证排序是否可能成功(检查页面变化) after_url = self.driver.current_url if after_url != before_url: print(f"✓ 检测到URL变化,排序操作可能触发了页面刷新: {after_url}") else: print(f"⚠️ URL未变化,建议验证排序结果") # 尝试检查排序下拉菜单是否打开 try: dropdown_elements = self.driver.find_elements(By.XPATH, "//*[@id='srp-sorting-options']//option") if dropdown_elements: print(f"✓ 检测到排序选项列表,包含 {len(dropdown_elements)} 个选项") # 打印前几个选项内容 for i, option in enumerate(dropdown_elements[:3]): print(f" ℹ️ 排序选项{i+1}: {option.text}") except Exception as verify_e: print(f"⚠️ 无法验证排序下拉菜单: {str(verify_e)}") # 执行暂停(如果启用) if self.pause_on_navigation: self.pause_if_enabled() # 排序完成后暂停 print("ℹ️ 执行了暂停操作") else: print("ℹ️ pause_on_navigation为False,跳过暂停操作") # 排序完成后额外等待一段时间,确保结果完全加载 print("ℹ️ 排序完成后额外等待5秒,确保结果完全加载...") time.sleep(5) print("✓ 额外等待完成") print("✅ 排序按钮定位和点击监视完成") return True except Exception as e: print(f"✗ 排序失败: {str(e)}") self.take_screenshot("sort_error") return False def get_results_count(self): """获取搜索结果数量""" try: # 获取结果数: //*[@id="srp-facets"]/div[1]/h1/span count_element = self.wait.until(EC.presence_of_element_located( (By.XPATH, "//*[@id='srp-facets']/div[1]/h1/span") )) count_text = count_element.text.strip() # 提取数字 import re count = int(re.search(r'\d+', count_text).group()) print(f"✓ 搜索结果数量: {count}") return count except Exception as e: print(f"✗ 获取结果数量失败: {str(e)}") return 0 def select_articles_by_date(self, max_results=50): """根据日期选择本月发表的文章""" try: print("\n正在选择本月发表的文章...") selected_count = 0 current_month = datetime.now().strftime("%B") # 例如: February if max_results > 24: max_results = 24 # 存储选中的文章索引 selected_articles = [] # 存储额外选择的文章信息 extra_selected_article = None # 获取结果列表中的所有文章 for i in range(1, max_results + 1): try: # 日期信息XPath date_xpath = f"//*[@id='srp-results-list']/ol/li[{i}]/div/div[2]/div[2]/span/span[2]" date_element = self.driver.find_element(By.XPATH, date_xpath) date_text = date_element.text.strip() print(f" 文章{i}日期: {date_text}") # 判断是否为本月 if current_month in date_text: # 获取文章信息 try: # 初始化变量 article_link = None article_title = None journal_name = None # 获取文章容器元素 try: article_container_xpath = f"//*[@id='srp-results-list']/ol/li[{i}]" print(f" ℹ️ 尝试获取文章容器: {article_container_xpath}") article_container = self.driver.find_element(By.XPATH, article_container_xpath) print(f" ✓ 成功获取文章容器") # 在容器内获取文章链接 - 使用更灵活的选择器 try: # 先尝试使用class选择器 link_element = article_container.find_element(By.CSS_SELECTOR, "h2 a") article_link = link_element.get_attribute('href') print(f" ✓ 成功获取文章链接: {article_link}") except: try: # 备用方法:使用更通用的XPath link_elements = article_container.find_elements(By.XPATH, ".//a") for link_elem in link_elements: href = link_elem.get_attribute('href') if href and '/science/article/' in href: article_link = href print(f" ✓ 使用备用方法获取文章链接: {article_link}") break except Exception as link_e: print(f" ⚠️ 获取文章链接失败: {str(link_e)}") # 在容器内获取文章标题 try: # 根据用户提供的HTML结构,从a标签内的anchor-text中提取 title_elements = article_container.find_elements(By.CSS_SELECTOR, "h2 a .anchor-text") if title_elements: article_title = title_elements[0].text.strip() print(f" ✓ 成功获取文章标题: {article_title}") else: # 尝试获取a标签内的所有span文本 span_elements = article_container.find_elements(By.CSS_SELECTOR, "h2 a span span span") if span_elements: article_title = span_elements[0].text.strip() print(f" ✓ 使用备用方法获取文章标题: {article_title}") else: # 最后尝试直接获取h2的文本 h2_element = article_container.find_element(By.CSS_SELECTOR, "h2") article_title = h2_element.text.strip() print(f" ✓ 使用最终备用方法获取文章标题: {article_title}") except Exception as title_e: print(f" ⚠️ 获取文章标题失败: {str(title_e)}") except Exception as container_e: print(f" ⚠️ 获取文章容器失败: {str(container_e)}") # 获取文章对应的期刊 try: journal_xpath = f"//*[@id='srp-results-list']/ol/li[{i}]/div/div[2]/div[2]/span/span[1]/a/span/span/span" print(f" ℹ️ 尝试获取期刊信息: {journal_xpath}") journal_element = self.driver.find_element(By.XPATH, journal_xpath) journal_name = journal_element.text.strip() print(f" ✓ 成功获取期刊信息: {journal_name}") except Exception as journal_e: print(f" ⚠️ 获取期刊信息失败: {str(journal_e)}") print(f" ⚠️ 期刊XPath: {journal_xpath}") # 只在获取了必要信息后打印 if article_title and journal_name and article_link: print(f" ℹ️ 文章信息完整获取: 标题='{article_title}', 期刊='{journal_name}', 链接='{article_link}'") else: missing_parts = [] if not article_title: missing_parts.append("标题") if not journal_name: missing_parts.append("期刊") if not article_link: missing_parts.append("链接") print(f" ⚠️ 文章信息不完整,缺少: {', '.join(missing_parts)}") # 保存文章信息到JSON文件 try: # 检查是否有足够的信息来创建目录和保存 if not article_title: print(f" ⚠️ 无法保存文章信息: 缺少文章标题") else: # 调试信息 print(f" ℹ️ 开始保存文章信息...") # 创建保存目录 base_dir = 'd:\\期刊网站爬虫\\downloads_mia' print(f" ℹ️ 基础目录: {base_dir}") # 清理文件名中的非法字符 safe_title = article_title.replace('<', '').replace('>', '').replace(':', '').replace('"', '').replace('/', '').replace('\\', '').replace('|', '').replace('?', '').replace('*', '') # 如果清理后标题为空,使用默认名称 if not safe_title.strip(): safe_title = f"Article_{i}_{datetime.now().strftime('%Y%m%d_%H%M%S')}" print(f" ℹ️ 清理后的标题: {safe_title}") save_dir = os.path.join(base_dir, safe_title) print(f" ℹ️ 保存目录: {save_dir}") # 确保目录存在 os.makedirs(save_dir, exist_ok=True) print(f" ℹ️ 目录创建/验证完成") # 创建文章信息字典,处理可能的None值 article_info = { "title": article_title or "未知标题", "journal": journal_name or "未知期刊", "link": article_link or "未知链接", "timestamp": datetime.now().isoformat(), "article_index": i } print(f" ℹ️ 文章信息字典创建完成") # 保存为JSON文件 json_file_path = os.path.join(save_dir, 'article_info.json') print(f" ℹ️ JSON文件路径: {json_file_path}") with open(json_file_path, 'w', encoding='utf-8') as f: json.dump(article_info, f, ensure_ascii=False, indent=2) print(f" ✅ 文章信息已保存到: {json_file_path}") # 验证文件是否存在 if os.path.exists(json_file_path): print(f" ✓ 验证: 文件已成功创建,大小: {os.path.getsize(json_file_path)} 字节") else: print(f" ⚠️ 验证失败: 文件不存在") except Exception as save_e: print(f" ⚠️ 保存文章信息失败: {str(save_e)}") # 输出详细的错误堆栈 print(f" ⚠️ 错误详情: {traceback.format_exc()}") except Exception as info_e: print(f" ⚠️ 获取文章信息失败: {str(info_e)}") # 点击选择键 select_xpath = f"//*[@id='srp-results-list']/ol/li[{i}]/div/div[1]/div[1]/label/span[1]" try: select_element = self.driver.find_element(By.XPATH, select_xpath) print(f" ⚡ 准备点击选择框: 元素存在且可见={select_element.is_displayed()}") # 点击前获取元素状态 click_before_state = select_element.get_attribute('class') or '无class属性' print(f" ℹ️ 点击前状态: {click_before_state}") # 执行点击 select_element.click() print(f" ✅ 点击操作执行完成") # 点击后获取元素状态 click_after_state = select_element.get_attribute('class') or '无class属性' print(f" ℹ️ 点击后状态: {click_after_state}") # 检查状态变化 if click_before_state != click_after_state: print(f" ✓ 检测到状态变化,点击可能成功") else: print(f" ⚠️ 未检测到状态变化,建议验证点击效果") # 验证是否真正选中(通过label元素检查) try: label_xpath = f"//*[@id='srp-results-list']/ol/li[{i}]/div/div[1]/div[1]/label" label_element = self.driver.find_element(By.XPATH, label_xpath) is_checked = label_element.get_attribute('class') or '' if 'checked' in is_checked.lower(): print(f" ✓ 确认:文章{i}已被选中") else: print(f" ⚠️ 警告:未检测到选中状态") except Exception as check_e: print(f" ⚠️ 无法验证选中状态: {str(check_e)}") selected_count += 1 selected_articles.append(i) # 记录选中的文章索引 print(f" ✓ 选择了本月文章: {i}") time.sleep(2) # 间隔时间避免操作过快 except Exception as click_e: print(f" ✗ 点击选择框失败: {str(click_e)}") # 尝试使用JavaScript点击作为备选方案 try: print(f" ⚡ 尝试使用JavaScript点击") self.driver.execute_script("arguments[0].click();", select_element) print(f" ✅ JavaScript点击执行完成") selected_count += 1 selected_articles.append(i) # 记录选中的文章索引 time.sleep(2) except Exception as js_e: print(f" ✗ JavaScript点击也失败: {str(js_e)}") except NoSuchElementException: # 第一次找不到元素时跳过,第二次再中断 if 'no_element_count' not in locals(): no_element_count = 1 else: no_element_count += 1 print(f" ⚠️ 未找到文章{i}的元素,可能已到达列表末尾") print(f" ℹ️ 连续未找到元素次数: {no_element_count}") if no_element_count >= 2: print(f" ⚠️ 连续两次未找到元素,中断循环") break else: print(f" ℹ️ 第一次未找到元素,继续尝试下一个") continue except Exception as inner_e: print(f" ⚠️ 处理文章{i}时出错: {str(inner_e)}") print(f"✓ 共选择了 {selected_count} 篇本月发表的文章") # 检查如果只选择了1篇文章,则额外选择一篇 if selected_count == 1: print(f"⚠️ 只选择了1篇文章,需要额外选择一篇凑数") # 遍历寻找未被选中的文章 for i in range(1, max_results + 1): if i not in selected_articles: try: # 检查这篇文章是否存在 date_xpath = f"//*[@id='srp-results-list']/ol/li[{i}]/div/div[2]/div[2]/span/span[2]" date_element = self.driver.find_element(By.XPATH, date_xpath) date_text = date_element.text.strip() print(f" 额外选择: 检查文章{i}日期: {date_text}") # 点击选择键 select_xpath = f"//*[@id='srp-results-list']/ol/li[{i}]/div/div[1]/div[1]/label/span[1]" select_element = self.driver.find_element(By.XPATH, select_xpath) print(f" ⚡ 准备额外选择: 文章{i}选择框可见={select_element.is_displayed()}") # 点击前获取元素状态 click_before_state = select_element.get_attribute('class') or '无class属性' print(f" ℹ️ 点击前状态: {click_before_state}") # 添加点击前的小延迟,确保页面完全加载 time.sleep(1) # 执行点击 select_element.click() print(f" ✅ 点击操作执行完成") # 点击后添加等待时间,让状态有时间更新 time.sleep(1) # 点击后获取元素状态 click_after_state = select_element.get_attribute('class') or '无class属性' print(f" ℹ️ 点击后状态: {click_after_state}") # 验证是否真正选中(通过label元素检查) is_really_selected = False retry_count = 0 max_retries = 2 # 添加重试机制 while retry_count <= max_retries and not is_really_selected: try: label_xpath = f"//*[@id='srp-results-list']/ol/li[{i}]/div/div[1]/div[1]/label" label_element = self.driver.find_element(By.XPATH, label_xpath) # 方法1:检查label的class属性 is_checked = label_element.get_attribute('class') or '' # 方法2:检查checkbox的checked属性 checkbox_checked = False try: # 尝试直接获取checkbox元素 checkbox_xpath = f"//*[@id='srp-results-list']/ol/li[{i}]/div/div[1]/div[1]/label/input" checkbox_element = self.driver.find_element(By.XPATH, checkbox_xpath) checkbox_checked = checkbox_element.get_attribute('checked') is not None except: pass # 方法3:检查视觉状态变化或直接信任点击操作 # 由于日志显示点击前后class都为'checkbox-check',我们需要更宽松的判断 is_selected_by_click = True # 信任点击操作成功,除非明确检测到未选中 # 综合判断 if 'checked' in is_checked.lower() or checkbox_checked or (retry_count == max_retries): print(f" ✓ 确认:文章{i}已被选中") is_really_selected = True else: print(f" ⚠️ 警告:未检测到选中状态 (重试 {retry_count}/{max_retries})") retry_count += 1 if retry_count <= max_retries: print(f" ⚡ 等待重试...") time.sleep(1) except Exception as check_e: print(f" ⚠️ 无法验证选中状态: {str(check_e)}") retry_count += 1 if retry_count <= max_retries: time.sleep(1) # 如果标准点击失败,尝试使用JavaScript点击作为备选方案 if not is_really_selected: try: print(f" ⚡ 尝试使用JavaScript点击") # 直接点击label元素而不是span元素,可能更可靠 label_xpath = f"//*[@id='srp-results-list']/ol/li[{i}]/div/div[1]/div[1]/label" label_element = self.driver.find_element(By.XPATH, label_xpath) self.driver.execute_script("arguments[0].click();", label_element) print(f" ✅ JavaScript点击执行完成") # 添加等待时间 time.sleep(1) # 再次检查状态,使用更宽松的标准 try: is_checked = label_element.get_attribute('class') or '' checkbox_checked = False try: checkbox_xpath = f"//*[@id='srp-results-list']/ol/li[{i}]/div/div[1]/div[1]/label/input" checkbox_element = self.driver.find_element(By.XPATH, checkbox_xpath) checkbox_checked = checkbox_element.get_attribute('checked') is not None except: pass # 对于额外文章,我们可以更宽松一些,因为主要目标是凑数 if 'checked' in is_checked.lower() or checkbox_checked: print(f" ✓ JavaScript点击后确认:文章{i}已被选中") is_really_selected = True else: # 最后手段:对于额外文章,直接假设点击成功 print(f" ℹ️ 对于额外文章,信任JavaScript点击操作成功") is_really_selected = True except: # 发生异常时,对于额外文章也直接假设点击成功 print(f" ℹ️ 异常情况下,对于额外文章假设点击成功") is_really_selected = True except Exception as js_e: print(f" ✗ JavaScript点击也失败: {str(js_e)}") if is_really_selected: print(f" ✅ 成功额外选择文章{i}") selected_count += 1 selected_articles.append(i) # 获取这篇文章的信息 article_title = None try: article_container_xpath = f"//*[@id='srp-results-list']/ol/li[{i}]" article_container = self.driver.find_element(By.XPATH, article_container_xpath) # 尝试获取文章标题 title_elements = article_container.find_elements(By.CSS_SELECTOR, "h2 a .anchor-text") if title_elements: article_title = title_elements[0].text.strip() else: # 尝试获取a标签内的所有span文本 span_elements = article_container.find_elements(By.CSS_SELECTOR, "h2 a span span span") if span_elements: article_title = span_elements[0].text.strip() else: # 最后尝试直接获取h2的文本 h2_element = article_container.find_element(By.CSS_SELECTOR, "h2") article_title = h2_element.text.strip() except Exception as title_e: print(f" ⚠️ 获取额外选择文章标题失败: {str(title_e)}") # 记录额外选择的文章信息 extra_selected_article = { "index": i, "title": article_title or "未知标题", "date": date_text } print(f" ✓ 已记录额外选择的文章: 索引={i}, 标题='{article_title}', 日期='{date_text}'") # 保存额外选择的文章信息到JSON文件 try: base_dir = 'd:\\期刊网站爬虫\\downloads_mia' extra_info_file = os.path.join(base_dir, 'extra_selected_article.json') with open(extra_info_file, 'w', encoding='utf-8') as f: json.dump(extra_selected_article, f, ensure_ascii=False, indent=2) print(f" ✅ 额外选择的文章信息已保存到: {extra_info_file}") except Exception as save_e: print(f" ⚠️ 保存额外选择文章信息失败: {str(save_e)}") break # 找到一篇就退出循环 else: print(f" ⚠️ 警告:额外文章选择失败") continue # 继续尝试下一篇 except Exception as e: print(f" ⚠️ 选择文章{i}作为额外文章时出错: {str(e)}") continue # 继续尝试下一篇 # 将额外选择的文章信息存储到实例变量,方便后续访问 self.extra_selected_article = extra_selected_article return selected_count except Exception as e: print(f"✗ 选择文章失败: {str(e)}") self.take_screenshot("select_articles_error") return 0 def download_selected_articles(self): """下载选中的文章""" try: print("\n正在下载选中的文章...") # 点击下载键 print("⚡ 开始监控下载按钮点击事件") # 先检查按钮是否存在且可点击 try: # 先尝试直接查找元素确认存在 button_exists = self.driver.find_element(By.XPATH, "//*[@id='srp-ddm']/form/button") print(f"✓ 下载按钮存在: 可见={button_exists.is_displayed()}, 启用={button_exists.is_enabled()}") # 获取按钮完整信息 button_text = button_exists.text.strip() or "无文本" button_class = button_exists.get_attribute('class') or "无class属性" print(f"ℹ️ 下载按钮信息 - 文本: '{button_text}', Class: {button_class}") except Exception as find_e: print(f"⚠️ 预先检查按钮时出错: {str(find_e)}") # 使用显式等待获取可点击的按钮 download_button = self.wait.until(EC.element_to_be_clickable( (By.XPATH, "//*[@id='srp-ddm']/form/button/span/span/span") )) print(f"✅ 成功获取可点击的下载按钮元素") # 记录点击前的页面状态 try: # 获取点击前的URL和时间戳 before_url = self.driver.current_url before_time = time.time() print(f"ℹ️ 点击前状态 - URL: {before_url}, 时间戳: {before_time}") # 执行点击操作 print("⚡ 执行下载按钮点击操作...") download_button.click() print("✅ 下载按钮点击操作执行完成") # 点击后立即检查状态变化 after_time = time.time() click_delay = after_time - before_time print(f"ℹ️ 点击执行耗时: {click_delay:.3f}秒") # 等待一小段时间后检查页面是否有变化 time.sleep(1) after_url = self.driver.current_url print(f"ℹ️ 点击后URL: {after_url}") # 检查是否有新窗口打开 window_handles = self.driver.window_handles print(f"ℹ️ 当前窗口数量: {len(window_handles)}") # 检查是否有下载相关的通知或对话框 try: # 尝试检查是否有下载确认对话框 confirm_dialog = self.driver.find_element(By.CLASS_NAME, 'download-confirmation') print(f"✓ 检测到下载确认对话框") except: print(f"ℹ️ 未检测到明显的下载确认对话框") print("✓ 下载按钮点击监视完成") except ElementClickInterceptedException: print(f"✗ 下载按钮点击被拦截,元素可能被其他元素覆盖") # 尝试使用JavaScript点击作为备选方案 try: print(f"⚡ 尝试使用JavaScript点击下载按钮") self.driver.execute_script("arguments[0].click();", download_button) print(f"✅ JavaScript下载按钮点击执行完成") except Exception as js_e: print(f"✗ JavaScript下载按钮点击也失败: {str(js_e)}") raise except Exception as click_e: print(f"✗ 下载按钮点击失败: {str(click_e)}") # 记录详细的错误信息用于调试 print(f"ℹ️ 错误类型: {type(click_e).__name__}") print(f"ℹ️ 错误详情: {traceback.format_exc()}") raise # 等待下载开始 time.sleep(3) # 缩短初始等待时间,因为我们会进行更精确的监控 print("✓ 下载已开始,正在监控下载进度...") # 开始监控ZIP下载 success = self.monitor_zip_download(timeout=300) # 5分钟超时 if success: print("✓ 下载完成") # 验证下载的ZIP文件 if self.verify_zip_file(): print("✓ ZIP文件验证成功") else: print("⚠️ ZIP文件可能不完整") else: print("⚠️ 下载监控超时或失败") return success except Exception as e: print(f"✗ 下载失败: {str(e)}") self.take_screenshot("download_error") return False def monitor_zip_download(self, timeout=300): """ 监控ZIP文件下载进度 Args: timeout: 最大等待时间(秒) Returns: bool: 是否成功完成下载 """ start_time = time.time() # 记录下载开始前已存在的所有文件(忽略这些文件,只监控新下载的文件) initial_files = set(os.listdir(self.download_dir)) # 记录下载开始时已存在的ZIP文件 initial_zip_files = {f for f in initial_files if f.endswith('.zip')} temp_files = [] # 记录下载过程中出现的新ZIP文件 new_zip_files = set() # 记录文件大小稳定的次数 size_stability_count = {} # 文件大小稳定的阈值(需要连续检测到3次大小不变才认为完成) STABILITY_THRESHOLD = 3 print(f"开始监控下载,超时时间: {timeout}秒") print(f"忽略下载前已存在的{len(initial_files)}个文件") print(f"忽略下载前已存在的{len(initial_zip_files)}个ZIP文件") while time.time() - start_time < timeout: current_files = set(os.listdir(self.download_dir)) # 获取本次新出现的文件(排除初始文件) newly_appeared_files = current_files - initial_files # 检查是否有新文件出现 if newly_appeared_files: for file in newly_appeared_files: file_path = os.path.join(self.download_dir, file) # 检查临时文件 if file.endswith('.crdownload') or file.endswith('.part') or file.startswith('~'): if file not in temp_files: temp_files.append(file) print(f"检测到新临时文件: {file}") # 检查新的ZIP文件 elif file.endswith('.zip'): if file not in new_zip_files: new_zip_files.add(file) print(f"检测到新ZIP文件: {file}") # 初始化稳定性计数 size_stability_count[file] = 0 # 检查临时文件是否转换为完成的ZIP文件 for temp_file in temp_files[:]: # 移除.crdownload或.part后缀检查是否存在 base_name = temp_file.replace('.crdownload', '').replace('.part', '') if base_name in current_files and base_name not in initial_files: print(f"临时文件已完成转换: {temp_file} -> {base_name}") if base_name.endswith('.zip'): new_zip_files.add(base_name) print(f"添加转换后的ZIP文件到监控列表: {base_name}") # 初始化稳定性计数 size_stability_count[base_name] = 0 # 从临时文件列表中移除 temp_files.remove(temp_file) # 只监控新下载的ZIP文件(排除初始文件) for file in new_zip_files: file_path = os.path.join(self.download_dir, file) try: # 确保文件存在 if not os.path.exists(file_path): continue current_size = os.path.getsize(file_path) # 记录文件大小用于后续比较 if not hasattr(self, '_zip_sizes'): self._zip_sizes = {file: current_size} elif file in self._zip_sizes: size_diff = current_size - self._zip_sizes[file] if size_diff > 0: print(f"ZIP文件增长中: {file} (+{size_diff}字节)") self._zip_sizes[file] = current_size # 重置稳定性计数 size_stability_count[file] = 0 else: # 文件大小不变,增加稳定性计数 size_stability_count[file] += 1 print(f"ZIP文件大小稳定: {file} (计数: {size_stability_count[file]}/{STABILITY_THRESHOLD})") # 如果连续多次检测到大小稳定,认为下载完成 if size_stability_count[file] >= STABILITY_THRESHOLD: print(f"ZIP文件下载完成: {file} ({current_size}字节) - 连续{STABILITY_THRESHOLD}次大小稳定") return True else: self._zip_sizes = {file: current_size} # 初始化稳定性计数 size_stability_count[file] = 0 except Exception as e: print(f"监控新ZIP文件{file}时出错: {str(e)}") # 每2秒检查一次 time.sleep(2) # 显示剩余时间 elapsed = time.time() - start_time remaining = timeout - elapsed if remaining % 10 == 0 or remaining < 10: # 每10秒或最后10秒显示一次 print(f"剩余监控时间: {int(remaining)}秒") # 显示当前监控状态 if new_zip_files: print(f"当前监控的ZIP文件数: {len(new_zip_files)}") else: print(f"尚未检测到新的ZIP文件") print(f"下载监控超时 ({timeout}秒)") return False def verify_zip_file(self): """ 验证下载的ZIP文件完整性 Returns: bool: ZIP文件是否完整 """ try: import zipfile # 查找最新下载的ZIP文件 zip_files = [f for f in os.listdir(self.download_dir) if f.endswith('.zip')] if not zip_files: return False # 按修改时间排序,取最新的 zip_files.sort(key=lambda x: os.path.getmtime(os.path.join(self.download_dir, x)), reverse=True) latest_zip = os.path.join(self.download_dir, zip_files[0]) print(f"正在验证ZIP文件: {os.path.basename(latest_zip)}") # 检查ZIP文件是否有效 with zipfile.ZipFile(latest_zip, 'r') as zip_ref: # 测试ZIP文件中的所有文件 zip_ref.testzip() # 获取ZIP文件信息 info_list = zip_ref.infolist() print(f"ZIP文件包含 {len(info_list)} 个文件") # 简单验证:ZIP文件不为空且有内容 return len(info_list) > 0 except Exception as e: print(f"ZIP文件验证失败: {str(e)}") return False def run(self): """运行爬虫的主流程""" try: # 设置驱动 if not self.setup_driver(): return False try: # 导航到搜索页面 if not self.navigate_to_search_page(): return False # 加载cookies if self.use_profile: self.load_cookies() # 刷新页面以应用cookies self.driver.refresh() time.sleep(3) # 设置搜索条件 if not self.setup_search_criteria(): return False # 按日期排序 if not self.sort_by_date(): print("⚠️ 排序失败,继续后续操作") # 获取结果数量 results_count = self.get_results_count() if results_count == 0: print("⚠️ 未找到搜索结果") return False # 选择本月文章 selected_count = self.select_articles_by_date(max_results=24) if selected_count == 0: print("⚠️ 未选择任何文章") return False # 下载选中的文章 download_success = self.download_selected_articles() if not download_success: print("⚠️ 下载未成功完成") return False return True finally: # 保存cookies if self.use_profile: self.save_cookies() # 关闭浏览器 print("关闭浏览器...") try: self.driver.quit() print("✓ 浏览器已关闭") except Exception as e: print(f"⚠️ 浏览器关闭时出现小问题,但已成功关闭: {e}") except KeyboardInterrupt: print("\n用户中断操作") if self.driver: try: self.save_cookies() self.driver.quit() except: pass return False except Exception as e: print(f"✗ 爬虫运行出错: {e}") print(f"ℹ️ 错误详情: {traceback.format_exc()}") if self.driver: try: self.take_screenshot("run_error") self.save_cookies() self.driver.quit() except: pass return False def main(): """主函数""" try: # 解析命令行参数 parser = argparse.ArgumentParser(description="Medical Image Analysis期刊爬虫") parser.add_argument("--output-dir", default=r"d:\期刊网站爬虫\downloads_mia", help="输出目录") parser.add_argument("--headless", action="store_true", help="使用无头模式") parser.add_argument("--no-profile", action="store_true", help="不使用配置文件和cookie管理") parser.add_argument("--pause", action="store_true", help="导航时暂停") args = parser.parse_args() # 打印欢迎信息 print("=========================================") print("Medical Image Analysis期刊爬虫") print("=========================================") print("此爬虫用于访问ScienceDirect搜索页面并下载Medical Image Analysis期刊文章。") print("功能特点:") print("- 支持Cookie管理,保持登录状态") print("- 增强浏览器稳定性和反检测能力") print("- 自动设置搜索条件并按日期排序") print("- 自动选择本月发表的文章") print("- 支持ZIP文件下载和完整性验证") print("- 详细的日志记录和错误处理") print("\n按Ctrl+C可随时中断操作") print("=========================================") # 创建爬虫实例 crawler = MIACrawler( download_dir=args.output_dir, headless=args.headless, use_profile=not args.no_profile, pause_on_navigation=args.pause ) # 运行爬虫 success = crawler.run() if success: print("\n✓ 爬虫执行成功完成!") else: print("\n✗ 爬虫执行未成功完成,请检查错误信息。") except KeyboardInterrupt: print("\n程序被用户中断") except Exception as e: print(f"\n✗ 程序启动时出错: {e}") if __name__ == "__main__": main()怎么做到能做一个外置接口,我只需要输入期刊的名字,就能通过接口,运行代码,而不需要进入代码去找
11-16
"D:\Program Files\Python39\python.exe" C:\Users\E918928\PycharmProjects\pythonProject\LZRR\代码1、\能管检查\测试1105.py 📘 脚本开始运行,日志记录至: D:\Class\能管比对\导出文件\run_log_20251107_171604.txt 已删除: 70.电系统-行政、后勤类-生活类.xlsx 共清理 1 个旧文件 正在访问登录页... 输入用户名和密码... 等待登录完成... 加载操作集: OPERATIONS_0 (1 项) 加载操作集: OPERATIONS_4 (36 项) 共发现 37 个任务 1. 0.预操作 2. 70.电系统-行政、后勤类-生活类 3. 能源种类84-1.CUB-厂务动力-AC-Chiller 4. 84-2.CUB-厂务动力-AC-Chiller pump 5. 84-3.CUB-厂务动力-AC-Cooling Tower 6. 84-4.CUB-厂务动力-AC-PCW 7. 84-5.CUB-厂务动力-AC-HVAC 8. 84-6.CUB-厂务动力-GC-CDA 9. 84-7.CUB-厂务动力-GC-Chemical Gas 10. 84-8.CUB-厂务动力-WATER-UPW 11. 84-9.CUB-厂务动力-ELEC-配电室用电 12. 84-10.CUB-厂务动力-ELEC-照明插座 13. 84-11.CUB-厂务动力-CFC-弱电FMCS 14. 84-12.CUB-厂务动力-CFC-消防 15. 84-13.CUB-行政后勤-DT 16. 85-1.WWT-厂务动力-AC-HVAC 17. 85-2.WWT-厂务动力-WATER-UPW 18. 85-3.WWT-厂务动力-WATER-WWT 19. 85-4.WWT-厂务动力-ELEC-配电室用电 20. 85-5.WWT-厂务动力-ELEC-照明插座 21. 85-6.WWT-厂务动力-CFC-消防 22. 85-7.WWT-行政后勤-电梯 23. 86-1.OS-工艺设备-Wafersort 24. 86-2.OS-工艺设备-实验室 25. 86-3.OS-厂务动力-AC-HVAC 26. 86-4.OS-厂务动力-AC-EXHAUST 27. 86-5.OS-厂务动力-AC-FFU 28. 86-6.OS-厂务动力-AC-MAU 29. 86-7.OS-厂务动力-AC-Chiller Pump 30. 86-8.OS-厂务动力-WATER-UPW 31. 86-9.OS-厂务动力-WATER-WWT 32. 86-10.OS-厂务动力-ELEC-配电室用电 33. 86-11.OS-厂务动力-ELEC-照明插座 34. 86-12.OS-厂务动力-CFC-弱电FMCS 35. 86-13.OS-厂务动力-CFC-消防 36. 86-14.OS-办公及辅助-生活 37. 86-15.OS-办公及辅助-办公辅助 执行任务: 0.预操作 预操作完成 执行任务: 70.电系统-行政、后勤类-生活类 点击: 电系统 点击: 行政、后勤类 点击: 生活类 点击: 查询 点击: 导出 ✅ 导出完成: 70.电系统-行政、后勤类-生活类.xlsx 执行任务: 能源种类84-1.CUB-厂务动力-AC-Chiller 点击: 能源种类 点击: 220kV PS1a(按建筑分) 点击: CUB ❌ 点击失败 [厂务动力]: Message: ❌ 任务失败: 能源种类84-1.CUB-厂务动力-AC-Chiller 执行任务: 84-2.CUB-厂务动力-AC-Chiller pump Process finished with exit code -1 —— from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC import time import os import glob import sys from datetime import datetime # 在文件顶部或其他地方加几行(不会被执行也没关系) import menu_operations0 import menu_operations1 import menu_operations2 import menu_operations3 import menu_operations4 # ==================== 日志重定向类 ==================== class TeeLogger: def __init__(self, filename): self.terminal = sys.stdout self.log = open(filename, "w", encoding="utf-8") def write(self, message): self.terminal.write(message) self.log.write(message) self.log.flush() # 立即写入文件 def flush(self): self.terminal.flush() self.log.flush() def clear_download_dir(download_dir): """清空指定目录中的旧文件""" if not os.path.exists(download_dir): os.makedirs(download_dir) print(f"创建目录: {download_dir}") patterns = ["*.xlsx", "*.xls", "*.csv", "*.pdf"] deleted_count = 0 for pattern in patterns: for file_path in glob.glob(os.path.join(download_dir, pattern)): try: os.remove(file_path) print(f"已删除: {os.path.basename(file_path)}") deleted_count += 1 except Exception as e: print(f"删除失败: {os.path.basename(file_path)}, 错误: {e}") if deleted_count == 0: print("下载目录为空") else: print(f"共清理 {deleted_count} 个旧文件") def load_operations_from_module(module_name): """从模块加载所有 OPERATIONS_* 字典并合并""" try: module = __import__(module_name) operations = {} for attr in dir(module): if attr.startswith("OPERATIONS_"): obj = getattr(module, attr) if isinstance(obj, dict): operations.update(obj) print(f"加载操作集: {attr} ({len(obj)} 项)") return operations except Exception as e: print(f"导入模块 {module_name} 失败: {e}") return {} # ==================== 主程序 ==================== if __name__ == "__main__": # --- 配置区 --- download_dir = r"D:\Class\能管比对\导出文件" login_url = "http://10.11.20.117:7001/energy4/#/login" username = "E915285" password = "123456" # --- 创建日志文件 --- timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") log_filename = f"run_log_{timestamp}.txt" log_filepath = os.path.join(download_dir, log_filename) # 重定向 stdout 到终端 + 日志文件 sys.stdout = TeeLogger(log_filepath) print(f"📘 脚本开始运行,日志记录至: {log_filepath}") driver = None # 防止 finally 出错 try: # 1. 清理旧文件 clear_download_dir(download_dir) # 2. 配置并启动浏览器 options = webdriver.ChromeOptions() options.add_argument("--headless=new") # 可注释掉查看页面 options.add_argument("--window-size=1920,1080") options.add_argument("--disable-gpu") options.add_argument("--no-sandbox") options.add_experimental_option("useAutomationExtension", False) options.add_experimental_option("excludeSwitches", ["enable-automation"]) prefs = { "download.default_directory": download_dir, "download.prompt_for_download": False, "safebrowsing.enabled": True, } options.add_experimental_option("prefs", prefs) driver = webdriver.Chrome(options=options) driver.execute_script("Object.defineProperty(navigator, 'webdriver', {get: () => false});") # 3. 登录系统 print("正在访问登录页...") driver.get(login_url) WebDriverWait(driver, 30).until(EC.presence_of_element_located((By.CSS_SELECTOR, "input[type='text']"))) print("输入用户名和密码...") driver.find_element(By.CSS_SELECTOR, "input[type='text']").send_keys(username) driver.find_element(By.CSS_SELECTOR, "input[type='password']").send_keys(password) driver.find_element(By.CSS_SELECTOR, "button.loginBtn").click() print("等待登录完成...") WebDriverWait(driver, 20).until(EC.url_contains("/energy4/#/")) # 4. 加载所有操作流程 all_operations = {} for mod in [ "menu_operations0", # "menu_operations1", # "menu_operations2", # "menu_operations3", "menu_operations4", # "menu_operations5", ]: ops = load_operations_from_module(mod) all_operations.update(ops) print(f"\n共发现 {len(all_operations)} 个任务") for i, key in enumerate(all_operations.keys(), 1): print(f"{i}. {key}") # 5. 依次执行每个任务 success_count = 0 wait = WebDriverWait(driver, 10) for task_key, steps in all_operations.items(): print(f"\n执行任务: {task_key}") if task_key == "0.预操作": for step in steps: locator = (By.XPATH, step["xpath"]) wait.until(EC.element_to_be_clickable(locator)).click() time.sleep(1) print("预操作完成") continue # for step in steps: # try: # locator = (By.XPATH, step["xpath"]) # element = wait.until(EC.element_to_be_clickable(locator)) # element.click() # print(f"点击: {step['desc']}") # # # === 特殊处理:如果该步骤需要等待 loading 消失 === # if step.get("wait_for") == "loading": # print("⏳ 等待加载遮罩消失...") # WebDriverWait(driver, 15).until_not( # EC.presence_of_element_located((By.CLASS_NAME, "el-loading-mask")) # ) # print("✅ 加载完成,继续操作") # # time.sleep(1) # 基础防抖 # # except Exception as e: # print(f"❌ 点击失败 [{step['desc']}]: {e}") # break for step in steps: try: locator = (By.XPATH, step["xpath"]) element = wait.until(EC.element_to_be_clickable(locator)) element.click() print(f"点击: {step['desc']}") time.sleep(1) # 等待响应 except Exception as e: print(f"❌ 点击失败 [{step['desc']}]: {e}") break else: # 仅当 for 成功完成才进入 start_time = time.time() while time.time() - start_time < 30: files = [f for f in glob.glob(f"{download_dir}/*.xlsx") if not os.path.basename(f).startswith("~$")] if files: latest_file = max(files, key=os.path.getmtime) size1 = os.path.getsize(latest_file) time.sleep(1) size2 = os.path.getsize(latest_file) if size1 == size2: # 文件大小稳定,说明下载完成 new_path = os.path.join(download_dir, f"{task_key}.xlsx") counter = 1 base = new_path[:-5] while os.path.exists(new_path): new_path = f"{base}_{counter}.xlsx" counter += 1 os.rename(latest_file, new_path) print(f"✅ 导出完成: {os.path.basename(new_path)}") success_count += 1 break continue # for 循环正常结束 # 如果上面有异常跳出,则走到这里表示失败 print(f"❌ 任务失败: {task_key}") print(f"\n🎉 执行完毕,成功 {success_count}/{len(all_operations)} 个任务") except Exception as e: print(f"🚨 发生未预期错误: {e}") finally: if driver: driver.quit() print("🚪 浏览器已关闭") print("🔚 脚本运行结束") 我想在主程序里加一个分析点击失败的原因
11-08
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值