linkedin-reader.js
3.37 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
/**
* LinkedInReader — 解析 LinkedIn 官方导出包中的 Connections.csv
*
* CSV 字段:First Name, Last Name, Email Address, Company, Position, Connected On
* 返回:Array<{firstName, lastName, email, company, position, connectedOn}>
*/
const fs = require('fs');
class LinkedInReader {
constructor(filePath) {
this.filePath = filePath;
}
/**
* 解析 CSV 文件(纯内置模块实现)
* @returns {Array<{firstName, lastName, email, company, position, connectedOn}>}
*/
parse() {
const raw = fs.readFileSync(this.filePath, 'utf-8');
const lines = raw.split(/\r?\n/);
// 找到 header 行(跳过 LinkedIn 导出文件顶部的空行/注释行)
let headerIndex = -1;
for (let i = 0; i < lines.length; i++) {
if (lines[i].includes('First Name') && lines[i].includes('Last Name')) {
headerIndex = i;
break;
}
}
if (headerIndex === -1) {
throw new Error('找不到 CSV header 行,请确认文件格式为 LinkedIn Connections.csv');
}
const headers = this._parseCsvLine(lines[headerIndex]).map(h => h.trim());
const colIdx = {
firstName: headers.findIndex(h => h === 'First Name'),
lastName: headers.findIndex(h => h === 'Last Name'),
email: headers.findIndex(h => h === 'Email Address'),
company: headers.findIndex(h => h === 'Company'),
position: headers.findIndex(h => h === 'Position'),
connectedOn: headers.findIndex(h => h === 'Connected On'),
};
const contacts = [];
for (let i = headerIndex + 1; i < lines.length; i++) {
const line = lines[i].trim();
if (!line) continue;
const cols = this._parseCsvLine(line);
if (cols.length < 2) continue;
contacts.push({
firstName: colIdx.firstName >= 0 ? (cols[colIdx.firstName] || '').trim() : '',
lastName: colIdx.lastName >= 0 ? (cols[colIdx.lastName] || '').trim() : '',
email: colIdx.email >= 0 ? (cols[colIdx.email] || '').trim() : '',
company: colIdx.company >= 0 ? (cols[colIdx.company] || '').trim() : '',
position: colIdx.position >= 0 ? (cols[colIdx.position] || '').trim() : '',
connectedOn: colIdx.connectedOn >= 0 ? (cols[colIdx.connectedOn] || '').trim() : '',
});
}
return contacts;
}
/**
* 解析单行 CSV(处理引号包裹、逗号、换行)
*/
_parseCsvLine(line) {
const cols = [];
let current = '';
let inQuotes = false;
for (let i = 0; i < line.length; i++) {
const ch = line[i];
if (ch === '"') {
if (inQuotes && line[i + 1] === '"') {
// 转义双引号
current += '"';
i++;
} else {
inQuotes = !inQuotes;
}
} else if (ch === ',' && !inQuotes) {
cols.push(current);
current = '';
} else {
current += ch;
}
}
cols.push(current);
return cols;
}
}
module.exports = { LinkedInReader };