1
This commit is contained in:
197
_script/import_company_info.js
Normal file
197
_script/import_company_info.js
Normal file
@@ -0,0 +1,197 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
/**
|
||||
* 导入公司信息 SQL 生成脚本
|
||||
* 从 _doc/公司xinxi.md 文件读取数据并生成 SQL INSERT 语句
|
||||
*/
|
||||
|
||||
// 转义 SQL 字符串中的特殊字符
|
||||
function escapeSql(str) {
|
||||
if (!str) return '';
|
||||
return str.replace(/'/g, "''").replace(/\\/g, '\\\\');
|
||||
}
|
||||
|
||||
// 从注册地址提取省份和城市
|
||||
function extractProvinceAndCity(address) {
|
||||
if (!address) return { province: '', city: '' };
|
||||
|
||||
// 大部分都是上海的公司
|
||||
if (address.includes('上海')) {
|
||||
return { province: '上海', city: '上海' };
|
||||
}
|
||||
|
||||
// 处理其他省份(如果有)
|
||||
const provincePatterns = [
|
||||
{ pattern: /^(.+?省|.+?市|.+?自治区|.+?特别行政区)/, extract: (match) => match[1] }
|
||||
];
|
||||
|
||||
for (const { pattern, extract } of provincePatterns) {
|
||||
const match = address.match(pattern);
|
||||
if (match) {
|
||||
const province = extract(match);
|
||||
return { province, city: province };
|
||||
}
|
||||
}
|
||||
|
||||
return { province: '', city: '' };
|
||||
}
|
||||
|
||||
// 读取文件并解析数据
|
||||
function parseCompanyData() {
|
||||
const filePath = path.join(__dirname, '../_doc/公司xinxi.md');
|
||||
const content = fs.readFileSync(filePath, 'utf-8');
|
||||
const lines = content.split('\n');
|
||||
|
||||
const companies = [];
|
||||
|
||||
// 跳过表头(第一行)
|
||||
for (let i = 1; i < lines.length; i++) {
|
||||
const line = lines[i].trim();
|
||||
if (!line) continue;
|
||||
|
||||
// 按制表符分割
|
||||
const parts = line.split('\t');
|
||||
if (parts.length < 7) continue;
|
||||
|
||||
const sequenceNumber = parts[0] || '';
|
||||
const stockCode = parts[1] || '';
|
||||
const companyName = parts[2] || '';
|
||||
const registeredAddress = parts[3] || '';
|
||||
const phone = parts[4] || '';
|
||||
const email = parts[5] || '';
|
||||
const website = parts[6] || '';
|
||||
|
||||
// 提取省份和城市
|
||||
const { province, city } = extractProvinceAndCity(registeredAddress);
|
||||
|
||||
// 判断是否上市(有证券代码就是上市)
|
||||
const isListed = stockCode ? 1 : 0;
|
||||
|
||||
companies.push({
|
||||
sequence_number: sequenceNumber,
|
||||
stock_code: stockCode,
|
||||
company_name: companyName,
|
||||
registered_address: registeredAddress,
|
||||
province,
|
||||
city,
|
||||
phone,
|
||||
email,
|
||||
website,
|
||||
is_listed: isListed,
|
||||
recommendation_level: 'normal',
|
||||
is_enabled: 1
|
||||
});
|
||||
}
|
||||
|
||||
return companies;
|
||||
}
|
||||
|
||||
// 生成 SQL INSERT 语句
|
||||
function generateSql(companies) {
|
||||
const sqlStatements = [];
|
||||
|
||||
// 添加注释
|
||||
sqlStatements.push('-- 导入公司信息数据');
|
||||
sqlStatements.push(`-- 生成时间: ${new Date().toLocaleString('zh-CN')}`);
|
||||
sqlStatements.push(`-- 数据条数: ${companies.length}`);
|
||||
sqlStatements.push('');
|
||||
sqlStatements.push('-- 开始事务(可选)');
|
||||
sqlStatements.push('-- START TRANSACTION;');
|
||||
sqlStatements.push('');
|
||||
|
||||
// 生成 INSERT 语句
|
||||
companies.forEach((company, index) => {
|
||||
const values = [
|
||||
company.sequence_number || 'NULL',
|
||||
company.stock_code ? `'${escapeSql(company.stock_code)}'` : 'NULL',
|
||||
`'${escapeSql(company.company_name)}'`,
|
||||
company.registered_address ? `'${escapeSql(company.registered_address)}'` : 'NULL',
|
||||
company.province ? `'${escapeSql(company.province)}'` : 'NULL',
|
||||
company.city ? `'${escapeSql(company.city)}'` : 'NULL',
|
||||
company.phone ? `'${escapeSql(company.phone)}'` : 'NULL',
|
||||
company.email ? `'${escapeSql(company.email)}'` : 'NULL',
|
||||
company.website ? `'${escapeSql(company.website)}'` : 'NULL',
|
||||
company.is_listed,
|
||||
`'${escapeSql(company.recommendation_level)}'`,
|
||||
company.is_enabled,
|
||||
'NULL' // remark
|
||||
];
|
||||
|
||||
const sql = `INSERT INTO company_info (
|
||||
sequence_number,
|
||||
stock_code,
|
||||
company_name,
|
||||
registered_address,
|
||||
province,
|
||||
city,
|
||||
phone,
|
||||
email,
|
||||
website,
|
||||
is_listed,
|
||||
recommendation_level,
|
||||
is_enabled,
|
||||
remark
|
||||
) VALUES (
|
||||
${values.join(',\n ')}
|
||||
);`;
|
||||
|
||||
sqlStatements.push(sql);
|
||||
|
||||
// 每 100 条添加一个注释
|
||||
if ((index + 1) % 100 === 0) {
|
||||
sqlStatements.push(`-- 已处理 ${index + 1} 条数据`);
|
||||
sqlStatements.push('');
|
||||
}
|
||||
});
|
||||
|
||||
sqlStatements.push('');
|
||||
sqlStatements.push('-- 提交事务(可选)');
|
||||
sqlStatements.push('-- COMMIT;');
|
||||
|
||||
return sqlStatements.join('\n');
|
||||
}
|
||||
|
||||
// 主函数
|
||||
function main() {
|
||||
try {
|
||||
console.log('开始解析公司信息数据...');
|
||||
const companies = parseCompanyData();
|
||||
console.log(`成功解析 ${companies.length} 条公司信息`);
|
||||
|
||||
console.log('生成 SQL 语句...');
|
||||
const sql = generateSql(companies);
|
||||
|
||||
// 保存到文件
|
||||
const outputPath = path.join(__dirname, '../_script/import_company_info.sql');
|
||||
fs.writeFileSync(outputPath, sql, 'utf-8');
|
||||
|
||||
console.log(`SQL 文件已生成: ${outputPath}`);
|
||||
console.log(`文件大小: ${(fs.statSync(outputPath).size / 1024).toFixed(2)} KB`);
|
||||
|
||||
// 显示统计信息
|
||||
const listedCount = companies.filter(c => c.is_listed === 1).length;
|
||||
const provinceStats = {};
|
||||
companies.forEach(c => {
|
||||
const p = c.province || '未知';
|
||||
provinceStats[p] = (provinceStats[p] || 0) + 1;
|
||||
});
|
||||
|
||||
console.log('\n统计信息:');
|
||||
console.log(`- 总数量: ${companies.length}`);
|
||||
console.log(`- 上市公司: ${listedCount}`);
|
||||
console.log(`- 未上市公司: ${companies.length - listedCount}`);
|
||||
console.log('\n省份分布:');
|
||||
Object.entries(provinceStats).forEach(([province, count]) => {
|
||||
console.log(` - ${province}: ${count}`);
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('生成 SQL 失败:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// 执行
|
||||
main();
|
||||
|
||||
13097
_script/import_company_info.sql
Normal file
13097
_script/import_company_info.sql
Normal file
File diff suppressed because it is too large
Load Diff
78
_script/import_company_info_README.md
Normal file
78
_script/import_company_info_README.md
Normal file
@@ -0,0 +1,78 @@
|
||||
# 公司信息数据导入说明
|
||||
|
||||
## 文件说明
|
||||
|
||||
- **生成脚本**: `_script/import_company_info.js`
|
||||
- **SQL 文件**: `_script/import_company_info.sql`
|
||||
- **数据源**: `_doc/公司xinxi.md`
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 方法一:使用 MySQL 命令行导入
|
||||
|
||||
```bash
|
||||
# 登录 MySQL
|
||||
mysql -u your_username -p your_database
|
||||
|
||||
# 执行 SQL 文件
|
||||
source _script/import_company_info.sql;
|
||||
|
||||
# 或者直接导入
|
||||
mysql -u your_username -p your_database < _script/import_company_info.sql
|
||||
```
|
||||
|
||||
### 方法二:使用数据库管理工具
|
||||
|
||||
1. 打开 Navicat、phpMyAdmin、DBeaver 等数据库管理工具
|
||||
2. 连接到目标数据库
|
||||
3. 打开 `_script/import_company_info.sql` 文件
|
||||
4. 执行 SQL 脚本
|
||||
|
||||
### 方法三:使用 Node.js 脚本导入(推荐)
|
||||
|
||||
可以创建一个 Node.js 脚本来执行导入,这样可以更好地处理错误和事务。
|
||||
|
||||
## 数据统计
|
||||
|
||||
- **总数量**: 451 条
|
||||
- **上市公司**: 451 条(所有公司都有证券代码)
|
||||
- **未上市公司**: 0 条
|
||||
- **省份分布**:
|
||||
- 上海: 448 条
|
||||
- 其他: 3 条
|
||||
|
||||
## 字段说明
|
||||
|
||||
- `sequence_number`: 序号
|
||||
- `stock_code`: 证券代码(如:300890.SZ)
|
||||
- `company_name`: 公司中文名称
|
||||
- `registered_address`: 注册地址
|
||||
- `province`: 省份(自动从注册地址提取)
|
||||
- `city`: 城市(自动从注册地址提取)
|
||||
- `phone`: 公司电话
|
||||
- `email`: 公司电子邮件地址
|
||||
- `website`: 公司网站
|
||||
- `is_listed`: 是否上市(1=上市,0=未上市)
|
||||
- `recommendation_level`: 推荐等级(默认:normal)
|
||||
- `is_enabled`: 是否启用(默认:1)
|
||||
- `remark`: 备注(默认:NULL)
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **事务处理**: SQL 文件中包含了事务注释,建议在执行前启用事务,以便在出错时回滚
|
||||
2. **数据验证**: 导入前请确保 `company_info` 表已创建
|
||||
3. **重复导入**: 如果数据已存在,可能需要先清空表或使用 `INSERT IGNORE` 或 `REPLACE INTO`
|
||||
4. **特殊字符**: 脚本已自动处理单引号等特殊字符的转义
|
||||
|
||||
## 重新生成 SQL
|
||||
|
||||
如果需要重新生成 SQL 文件(例如修改了数据源或字段映射),运行:
|
||||
|
||||
```bash
|
||||
node _script/import_company_info.js
|
||||
```
|
||||
|
||||
## 修改建议
|
||||
|
||||
如果需要修改导入逻辑(如推荐等级、是否上市等),可以编辑 `_script/import_company_info.js` 脚本。
|
||||
|
||||
234
_script/import_company_info_direct.js
Normal file
234
_script/import_company_info_direct.js
Normal file
@@ -0,0 +1,234 @@
|
||||
const Framework = require('../framework/node-core-framework.js');
|
||||
const frameworkConfig = require('../config/framework.config.js');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
/**
|
||||
* 直接导入公司信息到数据库
|
||||
* 逐条插入,出错时跳过并继续
|
||||
*/
|
||||
|
||||
// 从注册地址提取省份和区域
|
||||
function extractProvinceAndCity(address) {
|
||||
if (!address) return { province: '', city: '' };
|
||||
|
||||
// 提取省份和区域
|
||||
let province = '';
|
||||
let city = ''; // 这里实际存储的是区域(区),如:宝山区、杨浦区
|
||||
|
||||
// 匹配直辖市格式:XX市XX区...
|
||||
// 例如:上海市宝山区 -> province: "上海", city: "宝山区"
|
||||
const shMatch = address.match(/^(上海|北京|天津|重庆)市(.+?区)/);
|
||||
if (shMatch) {
|
||||
province = shMatch[1]; // 上海
|
||||
city = shMatch[2]; // 宝山区
|
||||
return { province, city };
|
||||
}
|
||||
|
||||
// 匹配其他省份格式:XX省XX市XX区...
|
||||
const provinceMatch = address.match(/^(.+?省)(.+?市)(.+?区)/);
|
||||
if (provinceMatch) {
|
||||
province = provinceMatch[1].replace('省', ''); // 去掉"省"字
|
||||
city = provinceMatch[3]; // 区域
|
||||
return { province, city };
|
||||
}
|
||||
|
||||
// 匹配省份/直辖市(没有区域信息)
|
||||
const simpleMatch = address.match(/^(.+?省|.+?市|.+?自治区|.+?特别行政区)/);
|
||||
if (simpleMatch) {
|
||||
province = simpleMatch[1].replace(/省|市|自治区|特别行政区$/, '');
|
||||
// 如果没有找到区域,city 保持为空
|
||||
}
|
||||
|
||||
return { province, city };
|
||||
}
|
||||
|
||||
// 读取文件并解析数据
|
||||
function parseCompanyData() {
|
||||
const filePath = path.join(__dirname, '../_doc/公司xinxi.md');
|
||||
const content = fs.readFileSync(filePath, 'utf-8');
|
||||
const lines = content.split('\n');
|
||||
|
||||
const companies = [];
|
||||
|
||||
// 跳过表头(第一行)
|
||||
for (let i = 1; i < lines.length; i++) {
|
||||
const line = lines[i].trim();
|
||||
if (!line) continue;
|
||||
|
||||
// 按制表符分割
|
||||
const parts = line.split('\t');
|
||||
if (parts.length < 7) continue;
|
||||
|
||||
const sequenceNumber = parts[0] || '';
|
||||
const stockCode = parts[1] || '';
|
||||
const companyName = parts[2] || '';
|
||||
const registeredAddress = parts[3] || '';
|
||||
const phone = parts[4] || '';
|
||||
const email = parts[5] || '';
|
||||
const website = parts[6] || '';
|
||||
|
||||
// 提取省份和城市
|
||||
const { province, city } = extractProvinceAndCity(registeredAddress);
|
||||
|
||||
// 判断是否上市(有证券代码就是上市)
|
||||
const isListed = stockCode ? 1 : 0;
|
||||
|
||||
companies.push({
|
||||
sequence_number: sequenceNumber ? parseInt(sequenceNumber) : null,
|
||||
stock_code: stockCode || null,
|
||||
company_name: companyName,
|
||||
registered_address: registeredAddress || null,
|
||||
province: province || null,
|
||||
city: city || null,
|
||||
phone: phone || null,
|
||||
email: email || null,
|
||||
website: website || null,
|
||||
is_listed: isListed,
|
||||
recommendation_level: 'normal',
|
||||
is_enabled: 1
|
||||
});
|
||||
}
|
||||
|
||||
return companies;
|
||||
}
|
||||
|
||||
// 主函数
|
||||
async function main() {
|
||||
let framework = null;
|
||||
let company_info = null;
|
||||
|
||||
try {
|
||||
console.log('🚀 开始导入公司信息数据...\n');
|
||||
|
||||
// 解析数据
|
||||
console.log('📖 正在解析数据文件...');
|
||||
const companies = parseCompanyData();
|
||||
console.log(`✅ 成功解析 ${companies.length} 条公司信息\n`);
|
||||
|
||||
// 初始化框架
|
||||
console.log('🔌 正在连接数据库...');
|
||||
framework = await Framework.init(frameworkConfig);
|
||||
const models = Framework.getModels();
|
||||
|
||||
if (!models) {
|
||||
throw new Error('无法获取模型列表');
|
||||
}
|
||||
|
||||
company_info = models.company_info;
|
||||
if (!company_info) {
|
||||
throw new Error('无法获取 company_info 模型');
|
||||
}
|
||||
|
||||
console.log('✅ 数据库连接成功\n');
|
||||
|
||||
// 统计信息
|
||||
let successCount = 0;
|
||||
let errorCount = 0;
|
||||
const errors = [];
|
||||
|
||||
// 逐条插入数据
|
||||
console.log('📝 开始插入数据...\n');
|
||||
|
||||
for (let i = 0; i < companies.length; i++) {
|
||||
const company = companies[i];
|
||||
const index = i + 1;
|
||||
|
||||
try {
|
||||
// 检查是否已存在(根据公司名称)
|
||||
const existing = await company_info.findOne({
|
||||
where: {
|
||||
company_name: company.company_name
|
||||
}
|
||||
});
|
||||
|
||||
if (existing) {
|
||||
// 如果已存在,更新区域信息
|
||||
if (company.city && company.city !== existing.city) {
|
||||
await company_info.update(
|
||||
{
|
||||
province: company.province,
|
||||
city: company.city
|
||||
},
|
||||
{
|
||||
where: { company_name: company.company_name }
|
||||
}
|
||||
);
|
||||
console.log(`🔄 [${index}/${companies.length}] 更新区域: ${company.company_name} -> ${company.city}`);
|
||||
} else {
|
||||
console.log(`⏭️ [${index}/${companies.length}] 跳过已存在: ${company.company_name}`);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// 插入数据
|
||||
await company_info.create(company);
|
||||
successCount++;
|
||||
|
||||
// 每 10 条显示一次进度
|
||||
if (index % 10 === 0 || index === companies.length) {
|
||||
console.log(`✅ [${index}/${companies.length}] 已插入 ${successCount} 条,跳过 ${errorCount} 条`);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
errorCount++;
|
||||
const errorMsg = `[${index}/${companies.length}] ${company.company_name}: ${error.message}`;
|
||||
errors.push(errorMsg);
|
||||
|
||||
// 显示错误(但继续执行)
|
||||
console.log(`❌ ${errorMsg}`);
|
||||
|
||||
// 如果错误太多,显示警告
|
||||
if (errorCount > 50 && errorCount % 50 === 0) {
|
||||
console.log(`⚠️ 警告: 已累计 ${errorCount} 个错误`);
|
||||
}
|
||||
}
|
||||
|
||||
// 每 100 条稍作延迟,避免数据库压力过大
|
||||
if (index % 100 === 0) {
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
}
|
||||
}
|
||||
|
||||
// 显示最终统计
|
||||
console.log('\n' + '='.repeat(60));
|
||||
console.log('📊 导入完成统计');
|
||||
console.log('='.repeat(60));
|
||||
console.log(`总数量: ${companies.length}`);
|
||||
console.log(`✅ 成功: ${successCount} 条`);
|
||||
console.log(`❌ 失败: ${errorCount} 条`);
|
||||
console.log(`⏭️ 跳过: ${companies.length - successCount - errorCount} 条(已存在)`);
|
||||
|
||||
if (errors.length > 0) {
|
||||
console.log('\n❌ 错误详情(前 20 条):');
|
||||
errors.slice(0, 20).forEach(err => console.log(` - ${err}`));
|
||||
if (errors.length > 20) {
|
||||
console.log(` ... 还有 ${errors.length - 20} 个错误未显示`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('\n✨ 导入任务完成!');
|
||||
|
||||
} catch (error) {
|
||||
console.error('\n❌ 导入失败:', error);
|
||||
console.error(error.stack);
|
||||
process.exit(1);
|
||||
} finally {
|
||||
// 关闭数据库连接
|
||||
if (framework && company_info && company_info.sequelize) {
|
||||
try {
|
||||
await company_info.sequelize.close();
|
||||
console.log('\n🔌 数据库连接已关闭');
|
||||
} catch (error) {
|
||||
console.error('关闭数据库连接时出错:', error.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 执行
|
||||
main().catch(error => {
|
||||
console.error('程序执行失败:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
0
_script/update_company_city.sql
Normal file
0
_script/update_company_city.sql
Normal file
Reference in New Issue
Block a user