环境准备与依赖安装
在开始编写代码之前,我们需要确保Python环境已经配置妥当,并安装处理Excel、PDF生成以及文件操作所需的第三方库。请打开终端或命令行工具,依次执行以下命令进行安装:
- pandas:用于高效读取和清洗Excel数据。
- openpyxl:用于支持.xlsx格式文件的读写操作。
- reportlab:用于生成标准PDF格式的档案文件。
安装命令如下:
```bash
pip install pandas openpyxl reportlab
```
安装完成后,建议创建一个名为express_archive的项目文件夹,后续的所有代码和数据文件都将存放在此处,以便于统一管理。
模拟原始快递数据源
为了确保读者能够直接运行代码并看到效果,我们首先编写一段Python脚本来生成一份包含“脏数据”的Excel文件。这份文件将模拟真实的快递档案,包含运单号、客户姓名、电话、地址、重量以及备注信息,其中故意留空部分单元格并设置格式错误,以测试清洗逻辑。
请在项目文件夹下创建0_create_sample_data.py,并写入以下完整代码:
```python
import pandas as pd
定义模拟数据
data = {
"运单号": ["SF1001", "YT2002", "SF1003", "ZT4004", "YT5005"],
"客户姓名": ["张三", "李四", "", "王五", "赵六"],
"联系电话": ["13800138000", "13900139000", "12345", "13700137000", "15900159000"],
"收货地址": ["北京市朝阳区", "上海市浦东新区", "广州市天河区", "", "深圳市南山区"],
"包裹重量": [2.5, 1.2, 3.0, 0.5, 4.2],
"备注": ["易碎品", "", "加急", "常规", "贵重物品"]
}
创建DataFrame并保存为Excel
df = pd.DataFrame(data)
file_path = 'raw_courier_data.xlsx'
df.to_excel(file_path, index=False)
print(f"模拟数据已生成:{file_path}")
```
运行该脚本后,文件夹中会出现raw_courier_data.xlsx文件,这就是我们接下来要整理的目标对象。
核心代码实现与逻辑解析
接下来是本次实操的核心部分。我们将编写一个主程序,完成以下三个任务:
- 数据清洗:剔除无效数据,补全默认值,校验手机号格式。
- PDF生成:将清洗后的每一条数据转换成排版整洁的PDF档案页。
- 自动归档:按照日期自动创建文件夹,并将生成的PDF文件移动至对应目录。
数据清洗模块

在处理原始Excel时,必须保证数据的规范性。我们将使用Pandas读取数据,并应用正则表达式来校验中国大陆手机号(11位数字,1开头)。对于缺失的姓名,我们标记为“未知客户”;对于缺失的地址,标记为“地址不详”。
核心清洗逻辑如下:
```python
import pandas as pd
import re
import os
from datetime import datetime
from reportlab.lib.pagesizes import A4
from reportlab.pdfgen import canvas
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
from reportlab.lib.units import cm
def clean_data(file_path):
读取Excel文件
try:
df = pd.read_excel(file_path)
except Exception as e:
print(f"读取文件失败: {e}")
return None
去除完全为空的行
df.dropna(how='all', inplace=True)
填充缺失值
df['客户姓名'].fillna('未知客户', inplace=True)
df['收货地址'].fillna('地址不详', inplace=True)
df['备注'].fillna('无', inplace=True)
校验手机号格式,简单的11位数字校验
def validate_phone(phone):
phone_str = str(phone)
移除可能的空格和横杠
phone_str = re.sub(r'[\s-]', '', phone_str)
if re.match(r'^1[3-9]\d{9}$', phone_str):
return phone_str
return "无效号码"
df['联系电话'] = df['联系电话'].apply(validate_phone)
过滤掉手机号无效的记录
valid_df = df[df['联系电话'] != "无效号码"].copy()
return valid_df
```
PDF档案生成模块
生成PDF是档案整理的关键。为了解决中文显示乱码的问题,我们需要注册中文字体。代码中会尝试查找系统中的常用中文字体(如SimHei或Arial Unicode MS)。如果找不到,请确保系统已安装对应字体或将字体文件放入项目目录。
我们将为每一条快递记录生成一个PDF文件,文件名包含运单号,便于检索。
```python
def setup_chinese_font():
"""注册中文字体,解决ReportLab中文乱码问题"""
font_name = "SimHei"
Windows常用中文字体路径
font_paths = [
"C:/Windows/Fonts/simhei.ttf",
"/System/Library/Fonts/PingFang.ttc", Mac
"/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc" Linux常见
]
registered = False
for path in font_paths:
if os.path.exists(path):
try:
pdfmetrics.registerFont(TTFont(font_name, path))
registered = True
break
except:
continue
如果系统字体找不到,尝试加载当前目录下的字体文件(需自行放置)
if not registered and os.path.exists("simhei.ttf"):
pdfmetrics.registerFont(TTFont(font_name, "simhei.ttf"))
registered = True
return font_name if registered else None
def generate_pdf(row, output_dir, font_name):
"""为单行数据生成PDF档案"""
tracking_no = row['运单号']
filename = f"{tracking_no}_档案.pdf"
file_path = os.path.join(output_dir, filename)
c = canvas.Canvas(file_path, pagesize=A4)
width, height = A4
if font_name:
c.setFont(font_name, 16)
else:
c.setFont("Helvetica", 16) 回退字体
绘制标题
c.drawString(5cm, height - 3cm, "快递面单档案")
绘制分割线
c.line(2cm, height - 3.5cm, width - 2cm, height - 3.5cm)
if font_name:
c.setFont(font_name, 12)
else:
c.setFont("Helvetica", 12)
绘制详细信息
start_y = height - 5cm
line_height = 1.2 cm
info = [
f"运单号: {tracking_no}",
f"客户姓名: {row['客户姓名']}",
f"联系电话: {row['联系电话']}",
f"收货地址: {row['收货地址']}",
f"包裹重量: {row['包裹重量']} kg",
f"备注信息: {row['备注']}",
f"归档日期: {datetime.now().strftime('%Y-%m-%d')}"
]
for text in info:
c.drawString(3cm, start_y, text)
start_y -= line_height
c.save()
return file_path
```
文件自动归档模块
为了实现档案的有序管理,我们将按照“年/月/日”的层级结构创建文件夹。例如,今天的档案将保存在archive/2023/10/27目录下。如果目录不存在,程序会自动创建。
```python
def organize_files(source_files):
"""将生成的文件移动到按日期分类的文件夹中"""
base_archive_dir = "archive"
today = datetime.now()
构建路径:archive/年/月/日
target_dir = os.path.join(base_archive_dir, str(today.year), f"{today.month:02d}", f"{today.day:02d}")
if not os.path.exists(target_dir):
os.makedirs(target_dir)
moved_files = []
for f_path in source_files:
if os.path.exists(f_path):
filename = os.path.basename(f_path)
dest_path = os.path.join(target_dir, filename)
重命名/移动文件
os.rename(f_path, dest_path)
moved_files.append(dest_path)
return target_dir, moved_files
```
完整代码整合与运行
将上述模块整合到一个主脚本中。请在项目文件夹下创建run_archive.py,并填入以下完整代码。这段代码串联了数据读取、清洗、PDF生成和归档的全流程。
```python
import pandas as pd
import re
import os
from datetime import datetime
from reportlab.lib.pagesizes import A4
from reportlab.pdfgen import canvas
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
1. 数据清洗函数
def clean_data(file_path):
df = pd.read_excel(file_path)
df.dropna(how='all', inplace=True)
df['客户姓名'].fillna('未知客户', inplace=True)
df['收货地址'].fillna('地址不详', inplace=True)
df['备注'].fillna('无', inplace=True)
def validate_phone(phone):
phone_str = str(re.sub(r'[\s-]', '', str(phone)))
return phone_str if re.match(r'^1[3-9]\d{9}$', phone_str) else "无效号码"
df['联系电话'] = df['联系电话'].apply(validate_phone)
return df[df['联系电话'] != "无效号码"].copy()
2. PDF生成函数
def setup_chinese_font():
font_name = "SimHei"
font_paths = [
"C:/Windows/Fonts/simhei.ttf",
"/System/Library/Fonts/PingFang.ttc",
"/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc",
"simhei.ttf" 当前目录
]
for path in font_paths:
if os.path.exists(path):
try:
pdfmetrics.registerFont(TTFont(font_name, path))
return font_name
except:
continue
return None
def generate_pdf(row, output_dir, font_name):
tracking_no = row['运单号']
filename = f"{tracking_no}_档案.pdf"
file_path = os.path.join(output_dir, filename)
c = canvas.Canvas(file_path, pagesize=A4)
width, height = A4
active_font = font_name if font_name else "Helvetica"
c.setFont(active_font, 16)
c.drawString(5cm, height - 3cm, "快递面单档案")
c.line(2cm, height - 3.5cm, width - 2cm, height - 3.5cm)
c.setFont(active_font, 12)
start_y = height - 5cm
info = [
f"运单号: {tracking_no}",
f"客户姓名: {row['客户姓名']}",
f"联系电话: {row['联系电话']}",
f"收货地址: {row['收货地址']}",
f"包裹重量: {row['包裹重量']} kg",
f"备注信息: {row['备注']}",
f"归档日期: {datetime.now().strftime('%Y-%m-%d')}"
]
for text in info:
c.drawString(3cm, start_y, text)
start_y -= 1.2cm
c.save()
return file_path
3. 自动归档函数
def organize_files(source_files):
base_archive_dir = "archive"
today = datetime.now()
target_dir = os.path.join(base_archive_dir, str(today.year), f"{today.month:02d}", f"{today.day:02d}")
if not os.path.exists(target_dir):
os.makedirs(target_dir)
moved_files = []
for f_path in source_files:
if os.path.exists(f_path):
dest_path = os.path.join(target_dir, os.path.basename(f_path))
os.rename(f_path, dest_path)
moved_files.append(dest_path)
return target_dir, moved_files
主程序入口
if __name__ == "__main__":
配置常量
INPUT_EXCEL = 'raw_courier_data.xlsx'
TEMP_OUTPUT_DIR = 'temp_pdfs'
初始化临时目录
if not os.path.exists(TEMP_OUTPUT_DIR):
os.makedirs(TEMP_OUTPUT_DIR)
print(">>> 开始处理快递档案...")
1. 清洗数据
print(">>> 正在读取并清洗Excel数据...")
df_clean = clean_data(INPUT_EXCEL)
if df_clean.empty:
print("错误:清洗后无有效数据,请检查源文件。")
exit()
2. 注册字体
font_name = setup_chinese_font()
if not font_name:
print("警告:未找到中文字体,PDF中中文可能无法显示。")
3. 批量生成PDF
print(f">>> 正在生成 {len(df_clean)} 个PDF档案...")
generated_files = []
for index, row in df_clean.iterrows():
pdf_path = generate_pdf(row, TEMP_OUTPUT_DIR, font_name)
generated_files.append(pdf_path)
4. 归档文件
print(">>> 正在执行自动归档...")
final_dir, archived_files = organize_files(generated_files)
清理临时空文件夹
if not os.listdir(TEMP_OUTPUT_DIR):
os.rmdir(TEMP_OUTPUT_DIR)
print(f">>> 处理完成!所有文件已归档至: {os.path.abspath(final_dir)}")
for f in archived_files:
print(f" - {os.path.basename(f)}")
```
运行与验证
确保项目文件夹中包含raw_courier_data.xlsx和run_archive.py。在终端中执行以下命令启动程序:
```bash
python run_archive.py
```
程序运行成功后,你将看到控制台输出处理进度,并在项目目录下生成一个名为archive的文件夹。打开该文件夹,你会看到按照“年份/月份/日期”自动创建的子目录,进入最内层目录,即可看到生成的PDF档案文件。打开任意PDF,你将看到排版整齐的快递面单信息,且无效的数据(如手机号为12345的记录)已被自动剔除。