Scrapy MySQL Неисправности ограничения внешнего ключа - PullRequest
0 голосов
/ 30 марта 2020

В настоящее время я работаю над созданием веб-скребка для веб-сайта под названием Mountain Project и столкнулся с проблемой при вставке элементов в базу данных MySQL (MariaDB).

Базовый c поток моего сканера таков:

  1. Получить ответ от экстрактора ссылок (я подклассифицирую CrawlSpider)
  2. Извлечь данные из ответа и отправить извлеченные элементы по элементу конвейер
  3. Элементы выбираются SqlPipeline и вставляются в базу данных

Важное замечание о шаге 2 заключается в том, что сканер отправляет несколько элементов вниз по трубопровод. Первый элемент будет основным ресурсом (либо маршрут, либо область), а затем любые другие элементы после него будут другими важными данными об этом ресурсе. Для областей эти элементы будут данными о различных погодных условиях, для маршрутов эти элементы будут иметь разные оценки, назначенные этим маршрутам.

Мои таблицы областей и маршрутов выглядят следующим образом:

CREATE TABLE `area` (
    `area_id` INT(10) UNSIGNED NOT NULL,
    `parent_id` INT(10) UNSIGNED NULL DEFAULT NULL,
    `name` VARCHAR(200) NOT NULL DEFAULT '',
    `latitude` FLOAT(12) NULL DEFAULT -1,
    `longitude` FLOAT(12) NULL DEFAULT -1,
    `elevation` INT(11) NULL DEFAULT '0',
    `link` VARCHAR(300) NOT NULL,
    PRIMARY KEY (`area_id`) USING BTREE
)

CREATE TABLE `route` (
    `route_id` INT(10) UNSIGNED NOT NULL,
    `parent_id` INT(10) UNSIGNED NOT NULL,
    `name` VARCHAR(200) NOT NULL DEFAULT '' COLLATE 'utf8_general_ci',
    `link` VARCHAR(300) NOT NULL COLLATE 'utf8_general_ci',
    `rating` FLOAT(12) NULL DEFAULT '0',
    `types` VARCHAR(50) NULL DEFAULT NULL COLLATE 'utf8_general_ci',
    `pitches` INT(3) NULL DEFAULT '0',
    `height` INT(5) NULL DEFAULT '0',
    `length` VARCHAR(5) NULL DEFAULT NULL COLLATE 'utf8_general_ci',
    PRIMARY KEY (`route_id`) USING BTREE,
    INDEX `fk_parent_id` (`parent_id`) USING BTREE,
    CONSTRAINT `fk_parent_id` FOREIGN KEY (`parent_id`) REFERENCES `mountainproject`.`area` (`area_id`) ON UPDATE CASCADE ON DELETE CASCADE
)

И вот пример одной из моих таблиц условий:

CREATE TABLE `temp_avg` (
    `month` INT(2) UNSIGNED NOT NULL,
    `area_id` INT(10) UNSIGNED NOT NULL,
    `avg_high` INT(3) NOT NULL,
    `avg_low` INT(3) NOT NULL,
    PRIMARY KEY (`month`, `area_id`) USING BTREE,
    INDEX `fk_area_id` (`area_id`) USING BTREE,
    CONSTRAINT `fk_area_id` FOREIGN KEY (`area_id`) REFERENCES `mountainproject`.`area` (`area_id`) ON UPDATE CASCADE ON DELETE CASCADE
)

Вот где вещи начинают становиться неприятными. Если я запускаю свой сканер и просто извлекаю области , все работает нормально. Область вставляется в базу данных, и все данные условий вставляются без проблем. Однако, когда я пытаюсь извлечь области и маршрутов, я получаю ошибки ограничения внешнего ключа при попытке вставить маршруты, потому что область, которой принадлежит маршрут (parent_id), не существует. В настоящее время, чтобы обойти это, я дважды запускаю свой гусеничный ход. Один раз, чтобы извлечь данные области, и один раз, чтобы извлечь данные маршрута. Если я это сделаю, все пойдет гладко.

Мое лучшее предположение относительно того, почему это не работает в настоящее время, состоит в том, что вставляемые области не были зафиксированы, и поэтому, когда я пытаюсь добавить маршрут, который принадлежит к незафиксированной области, она не может найти родительскую область. Однако эта теория быстро разваливается, потому что я могу вставить данные условия в тот же прогон, в который вставляю область, к которой принадлежат данные.

Мой код вставки выглядит следующим образом:

def insert_item(self, table_name, item):
    encoded_vals = [self.sql_encode(val) for val in item.values()]
    sql = "INSERT INTO %s (%s) VALUES (%s)" % (
        table_name,
        ", ".join(item.keys()),
        ", ".join(encoded_vals)
    )

    logging.debug(sql)
    self.cursor.execute(sql)

# EDIT: As suggested by @tadman I have moved to using the built in sql value
# encoding. I'm leaving this here because it doesn't affect the issue
def sql_encode(self, value):
    """Encode provided value and return a valid SQL value

    Arguments:
        value {Any} -- Value to encode

    Returns:
        str -- SQL encode value as a str
    """
    encoded_val = None
    is_empty = False

    if isinstance(value, str):
        is_empty = len(value) == 0

    encoded_val = "NULL" if is_empty or value is None else value

    if isinstance(encoded_val, str) and encoded_val is not "NULL":
        encode_val = encoded_val.replace("\"", "\\\"")
        encoded_val = "\"%s\"" % encoded_val

    return str(encoded_val)

Остальная часть проекта находится в репозитории GitHub , если требуется больше кода / контекста

...