2025最强数据采集指南:用Open Event Scraper实现Google表格到JSON的无缝转换

2025最强数据采集指南:用Open Event Scraper实现Google表格到JSON的无缝转换

你是否还在为手动解析Google表格数据而烦恼?面对复杂的会议日程、多轨道演讲安排,人工处理不仅耗时耗力,还容易出错。本文将带你深入了解Open Event Scraper项目,一个专为解析Google表格数据并转换为JSON格式而设计的强大工具。通过本文,你将掌握从环境搭建到高级应用的全过程,轻松应对各类数据采集需求。

读完本文,你将能够:

  • 快速搭建Open Event Scraper开发环境
  • 理解项目核心架构和工作流程
  • 掌握数据解析的关键技术和实现细节
  • 成功运行示例并定制自己的解析规则
  • 解决常见问题并优化性能

项目概述

Open Event Scraper是一个开源项目,主要用于解析Google Spreadsheet(电子表格)数据,并将其转换为结构化的JSON格式。该项目最初是为FOSSASIA 2016会议设计的,用于处理会议日程、演讲者信息和会场安排等数据。

核心功能

Open Event Scraper提供了以下关键功能:

功能描述
Google表格解析支持从Google表格中提取数据,包括多个工作表
数据结构化将原始表格数据转换为规范化的会话、演讲者和轨道信息
JSON生成输出符合Open Event格式的JSON数据,便于后续处理和展示
数据验证检查数据一致性,如重复的会话ID
时间处理支持时区转换和时间计算

技术栈

项目采用Python作为主要开发语言,结合了多种库和工具:

  • 核心语言: Python 2.x(注:可根据需要迁移至Python 3.x)
  • 数据处理: csv, json, simplejson
  • 网络请求: urllib2
  • 时间处理: datetime, pytz
  • 数据验证: validators
  • 对象序列化: jsonpickle

环境搭建

系统要求

  • Python 2.7(推荐使用虚拟环境)
  • 网络连接(用于获取Google表格数据)
  • Git(用于版本控制)

安装步骤

1. 克隆仓库
git clone https://gitcode.com/gh_mirrors/op/open-event-scraper
cd open-event-scraper
2. 安装依赖
pip install -r requirements.txt

requirements.txt文件内容:

jsonpickle
simplejson
validators
pytz
3. 配置项目

项目无需额外配置,但你可能需要根据自己的需求修改scraper.py中的常量,如:

  • SHEET_ID: Google表格ID
  • SHEET_VERSIONING_GID: 版本控制工作表ID
  • SESSION_LENGTH: 会话时长(默认30分钟)
  • TZ_LOCAL: 本地时区(默认亚洲/香港)
  • YEAR_OF_CONF: 会议年份(默认2016)

运行项目

./run.sh

run.sh脚本内容非常简单,直接调用主程序:

python ./scraper.py

项目架构

文件结构

open-event-scraper/
├── LICENSE
├── Procfile
├── README.md
├── __init__.py
├── models.py          # 数据模型定义
├── package.json
├── parser.py          # 解析辅助函数
├── requirements.txt   # 依赖列表
├── run.sh             # 启动脚本
├── scraper.py         # 主程序
├── schedule/          # 调度相关文件
│   ├── generator.js
│   └── schedule.tpl
└── web/               # Web相关文件
    └── hook.js

核心模块

1. 数据模型 (models.py)

该文件定义了四个主要类:

  • Track: 表示会议的一个轨道(如不同主题的分会场)
  • Speaker: 表示演讲者信息
  • Session: 表示一个会议会话
  • Microlocation: 表示微位置信息
class Track(object):
    id = 0
    header_line = 1
    filename = ""
    key_color = "#FF4D55"
    name = ""
    description = ""
    track_image_url = "http://lorempixel.com/400/200"
    location = ""
    gid = ""
    order = -1

    def __init__(self, id, name, header_line, key_color, location, gid, order):
        super(Track, self).__init__()
        self.id = id
        self.name = name
        self.header_line = header_line
        self.key_color = key_color
        self.track_image_url = "http://lorempixel.com/400/200"
        self.location = location
        self.gid = gid
        self.order = order
2. 主程序 (scraper.py)

这是项目的核心文件,包含了数据获取、解析和JSON生成的主要逻辑。

3. 解析辅助函数 (parser.py)

提供了一些辅助解析功能,如获取LinkedIn URL、处理图片URL等。

工作流程

Open Event Scraper的工作流程可以分为以下几个主要步骤:

mermaid

  1. 获取数据: 从Google表格API获取TSV格式的数据
  2. 解析轨道: 识别不同的会议轨道(Track)
  3. 解析会话: 为每个轨道解析会话(Session)和演讲者(Speaker)信息
  4. 数据验证: 检查数据一致性,如重复的会话ID
  5. 生成JSON: 将结构化数据输出为JSON文件

核心功能实现

1. Google表格数据获取

项目通过Google表格的导出功能获取数据,使用以下URL格式:

def fetch_tsv_data(gid):
    base_url = 'https://docs.google.com/spreadsheets/d/' + SHEET_ID + '/export?format=tsv'
    url = base_url + '&gid=' + gid
    logging.info('GET ' + url)
    res = urllib2.urlopen(url)
    return res.read()

其中,SHEET_ID是Google表格的唯一标识符,gid是工作表的ID。

2. 轨道解析

轨道(Track)代表会议的不同主题或分会场。解析轨道的主要步骤如下:

def parse_tracklist(track_data):
    tracks = []
    headers = []
    HEADER_LINE = 1

    i = 1
    track = None
    for line in csv.reader(track_data.split("\n"), delimiter="\t"):
        if i == HEADER_LINE:
            HEADERS = map(str.strip, line)
        elif i > HEADER_LINE:
            row = create_associative_arr(line, HEADERS)
            if not row["Header Line"]:
                continue
            track = Track(
                id = i,
                name = row["Track"],
                header_line = int(row["Header Line"]),
                key_color = row["Key Color"],
                location = row["Room"],
                gid = row["GID"],
                order = i
            )
            tracks.append(track)

        i = i + 1

    return tracks

3. 会话和演讲者解析

每个轨道包含多个会话,每个会话可能有一个或多个演讲者。解析逻辑如下:

def parse_sessions(track, session_data):
    HEADERS = []
    i = 1
    speaker = None
    session = None

    for line in csv.reader(session_data.split("\n"), delimiter="\t"):
        if i == track.header_line:
            HEADERS = map(str.strip, line)
        elif i > track.header_line:
            row = create_associative_arr(line, HEADERS)
            (res_speaker, res_session) = parse_row(row, speaker, session, track)
            if res_speaker is not None:
                speaker = res_speaker
            if res_session is not None:
                session = res_session
        i = i + 1

4. 行解析逻辑

解析每一行数据时,需要区分演讲者和会话信息:

def parse_row(row, last_speaker, last_session, current_track):
    session_id = row["SessionID"]

    # 没有会话ID则跳过此行
    if not session_id:
        return (None, None)

    # 新会话
    if last_session is None or last_session.session_id != session_id:
        session = Session()
        session.session_id = session_id
        session.speakers = []
        SESSIONS.append(session)
    else:
        session = last_session

    # 解析演讲者信息
    if row["Given Name"]:
        speaker = Speaker()
        speaker.organisation = row["Company, Organization, Project or University"]
        speaker.name = (row["Given Name"].strip() + " " + row["Family Name"].strip()).strip()
        # 其他演讲者属性...
        # ...

    # 解析会话信息
    session_time = parse_time(row["Date"] + " " + row["Time"])
    if session_time is not None:
        session.end_time = (session_time + SESSION_LENGTH).isoformat()
        if not hasattr(session, 'start_time'):
            session.start_time = session_time.isoformat()

    # 设置其他会话属性...
    # ...

    return (speaker, session)

5. 时间处理

项目支持时区转换和时间计算,确保不同地区的用户看到正确的时间:

TZ_UTC = pytz.utc
TZ_LOCAL = pytz.timezone('Asia/Hong_Kong')
SESSION_LENGTH = datetime.timedelta(minutes=30)

def parse_time(time_str):
    # 修复年份
    if YEAR_OF_CONF not in time_str:
        time_str = YEAR_OF_CONF + " " + time_str

    for date_format in DATE_FORMATS:
        try:
            iso_date = datetime.datetime.strptime(time_str, date_format)
        except ValueError as e:
            pass
        else:
            break

    if iso_date is None:
        return None

    local_date = TZ_LOCAL.localize(iso_date)
    utc_date = local_date.astimezone(TZ_UTC)

    return utc_date

6. JSON生成

解析完成后,项目将结构化数据转换为JSON格式:

def write_json(filename, root_key, the_json):
    f = open(filename + '.json', 'w')
    the_json = simplejson.dumps(simplejson.loads(the_json), indent=2, sort_keys=False)
    json_to_write = '{ "%s":%s}' % (root_key, the_json)
    f.write(json_to_write)
    f.close()

# 使用示例
speakers_json = jsonpickle.encode(SPEAKERS)
write_json('out/speakers', 'speakers', speakers_json)

session_json = jsonpickle.encode(SESSIONS)
write_json('out/sessions', 'sessions', session_json)

tracks_json = jsonpickle.encode(tracks)
write_json('out/tracks', 'tracks', tracks_json)

7. 数据验证

为确保数据质量,项目包含了简单的数据验证功能:

def validate_sessions(sessions): 
    logging.info('validating')

    s_map = {}
    dups = []

    for session in sessions:
        if session.session_id in s_map:
            s_map[session.session_id] = s_map[session.session_id] + 1
        else:
            s_map[session.session_id] = 1

    for session_id, count in s_map.iteritems():
        if count > 1:
            dups.append(session_id)

    if len(dups) > 0:
        logging.error('Duplicate session ids: ' + (', '.join(dups)))
        return False
    else:
        logging.info('All fine')
        return True

运行示例

基本用法

完成环境搭建后,可以通过以下命令运行项目:

./run.sh

run.sh的内容非常简单,直接调用scraper.py:

python ./scraper.py

输出结果

程序运行后,将在项目根目录下创建out文件夹,并生成以下JSON文件:

  • speakers.json: 演讲者信息
  • sessions.json: 会话信息
  • tracks.json: 轨道信息

示例输出(sessions.json片段):

{
  "sessions": [
    {
      "description": "Introduction to Open Event Scraper",
      "end_time": "2016-03-11T08:30:00+00:00",
      "location": "Main Hall",
      "session_id": "S001",
      "speakers": [
        {
          "id": 1,
          "name": "John Doe",
          "organisation": "FOSSASIA"
        }
      ],
      "start_time": "2016-03-11T08:00:00+00:00",
      "title": "Open Event Scraper: A Comprehensive Guide",
      "track": {
        "id": 1,
        "name": "Development",
        "order": 1
      },
      "type": "Talk"
    },
    // 更多会话...
  ]
}

高级应用与定制

1. 修改表格ID和工作表ID

要解析自己的Google表格,需要修改scraper.py中的以下常量:

SHEET_ID = 'your_google_sheet_id'
SHEET_VERSIONING_GID = 'your_sheet_gid'

你可以从Google表格的URL中获取这些ID:

https://docs.google.com/spreadsheets/d/[SHEET_ID]/edit#gid=[GID]

2. 自定义数据解析规则

如果你的表格结构与默认不同,可以修改解析函数:

  • parse_tracklist: 修改轨道解析规则
  • parse_sessions: 修改会话解析规则
  • parse_row: 修改行解析逻辑

3. 扩展数据模型

可以通过修改models.py来扩展数据模型,添加新的属性:

class Speaker(object):
    def __init__(self):
        super(Speaker, self).__init__()
        self.twitter = ""  # 添加Twitter账号
        self.github = ""   # 添加GitHub账号
        # 其他自定义属性...

4. 集成到其他项目

Open Event Scraper的输出是标准JSON格式,可以轻松集成到其他项目中,如:

  • 会议网站:用于展示日程和演讲者信息
  • 移动应用:作为后端数据源
  • 数据分析工具:用于会议数据统计和分析

常见问题与解决方案

1. 编码问题

问题: 解析非ASCII字符时出现编码错误。

解决方案: 在打开文件时指定编码:

import codecs
f = codecs.open(filename, 'r', 'utf-8')

2. Python版本兼容性

问题: 项目使用Python 2.x,在Python 3.x环境下运行出错。

解决方案: 迁移到Python 3.x,主要修改以下内容:

  • urllib2urllib.request
  • print语句 → print()函数
  • xrangerange

3. 表格访问权限问题

问题: 无法获取表格数据,出现403错误。

解决方案: 确保Google表格设置为"任何人可查看":

  1. 打开Google表格
  2. 点击"分享"按钮
  3. 设置"链接分享"为"任何有链接的人都可以查看"

4. 数据格式错误

问题: 解析错误,数据格式不符合预期。

解决方案:

  1. 检查表格结构是否与预期一致
  2. 启用详细日志,查看错误位置:
logging.getLogger().setLevel(logging.DEBUG)

项目优化与性能提升

1. 缓存机制

为避免重复获取相同的表格数据,可以添加缓存机制:

import os
import time

CACHE_DIR = 'cache'
CACHE_EXPIRE = 3600  # 缓存过期时间(秒)

def fetch_tsv_data(gid):
    # 检查缓存
    cache_file = os.path.join(CACHE_DIR, f"{gid}.tsv")
    if os.path.exists(cache_file) and time.time() - os.path.getmtime(cache_file) < CACHE_EXPIRE:
        with open(cache_file, 'r') as f:
            return f.read()
    
    # 获取新数据
    base_url = 'https://docs.google.com/spreadsheets/d/' + SHEET_ID + '/export?format=tsv'
    url = base_url + '&gid=' + gid
    logging.info('GET ' + url)
    res = urllib2.urlopen(url)
    data = res.read()
    
    # 保存缓存
    if not os.path.exists(CACHE_DIR):
        os.makedirs(CACHE_DIR)
    with open(cache_file, 'w') as f:
        f.write(data)
    
    return data

2. 多线程处理

对于包含多个工作表的大型表格,可以使用多线程并行获取和解析数据:

import threading

def process_track(track):
    logging.info("[Fetching Track] '%s', gid = %s", track.name, track.gid)
    data = fetch_tsv_data(track.gid)
    logging.info("[Parsing Track] '%s'", track.name)
    parse_sessions(track, data)

# 在主程序中使用多线程
threads = []
for track in tracks:
    if not track.gid:
        continue
    t = threading.Thread(target=process_track, args=(track,))
    threads.append(t)
    t.start()

# 等待所有线程完成
for t in threads:
    t.join()

项目贡献与扩展

如何贡献

Open Event Scraper是一个开源项目,欢迎社区贡献。你可以通过以下方式参与:

  1. 提交bug报告和功能请求
  2. 提交代码修复和改进
  3. 编写和改进文档
  4. 帮助迁移到Python 3.x

未来发展方向

  1. Python 3.x支持: 迁移代码以支持最新的Python版本
  2. API集成: 添加对Google Sheets API的直接支持
  3. 配置文件: 使用配置文件代替硬编码常量
  4. 数据可视化: 添加简单的数据可视化功能
  5. 更多输出格式: 支持CSV、XML等其他格式

总结

Open Event Scraper是一个功能强大的数据解析工具,特别适用于从Google表格中提取结构化数据。通过本文的介绍,你已经了解了项目的核心功能、架构设计和实现细节。从环境搭建到高级应用,从基本使用到性能优化,我们覆盖了使用该工具的各个方面。

无论是处理会议数据、收集调研结果,还是任何需要从表格中提取结构化信息的场景,Open Event Scraper都能为你提供高效、可靠的解决方案。希望本文能帮助你更好地理解和使用这个项目,为你的数据处理工作带来便利。

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值