Scrapy - Chiave esterna SQLalchemy non creata in SQLite
Ho provato a eseguire Scrapy utilizzando itemLoader per raccogliere tutti i dati e inserirli in SQLite 3. Sono riuscito a raccogliere tutte le informazioni che volevo ma non riesco a generare le chiavi esterne nelle mie tabelle ThreadInfo e PostInfo utilizzando back_populates
una chiave esterna. Ho provato back_ref
ma non ha funzionato. Tutte le altre informazioni sono state inserite nel database SQLite al termine del mio Scrapy.
Il mio obiettivo è avere quattro tabelle, boardInfo, threadInfo, postInfo e authorInfo collegati tra loro.
- boardInfo avrà una relazione uno-a-molti con threadInfo
- threadInfo avrà una relazione uno-a-molti con postInfo
- authorInfo avrà una relazione uno-a-molti con threadInfo e
postInfo.
Ho usato DB Browser per SQLite e ho scoperto che i valori delle mie chiavi esterne sono Null
. Ho provato la query per il valore (threadInfo.boardInfos_id) e viene visualizzato None
. Provo a risolvere questo problema per molti giorni e leggo il documento ma non riesco a risolvere il problema.
Come posso generare le chiavi foriegn nelle mie tabelle threadInfo e postInfo?
Grazie per tutte le guide e i commenti.
Ecco il mio models.py
from sqlalchemy import create_engine, Column, Table, ForeignKey, MetaData
from sqlalchemy import Integer, String, Date, DateTime, Float, Boolean, Text
from sqlalchemy.orm import relationship
from sqlalchemy.ext.declarative import declarative_base
from scrapy.utils.project import get_project_settings
Base = declarative_base()
def db_connect():
'''
Performs database connection using database settings from settings.py.
Returns sqlalchemy engine instance
'''
return create_engine(get_project_settings().get('CONNECTION_STRING'))
def create_table(engine):
Base.metadata.create_all(engine)
class BoardInfo(Base):
__tablename__ = 'boardInfos'
id = Column(Integer, primary_key=True)
boardName = Column('boardName', String(100))
threadInfosLink = relationship('ThreadInfo', back_populates='boardInfosLink') # One-to-Many with threadInfo
class ThreadInfo(Base):
__tablename__ = 'threadInfos'
id = Column(Integer, primary_key=True)
threadTitle = Column('threadTitle', String())
threadLink = Column('threadLink', String())
threadAuthor = Column('threadAuthor', String())
threadPost = Column('threadPost', Text())
replyCount = Column('replyCount', Integer)
readCount = Column('readCount', Integer)
boardInfos_id = Column(Integer, ForeignKey('boardInfos.id')) # Many-to-One with boardInfo
boardInfosLink = relationship('BoardInfo', back_populates='threadInfosLink') # Many-to-One with boardInfo
postInfosLink = relationship('PostInfo', back_populates='threadInfosLink') # One-to-Many with postInfo
authorInfos_id = Column(Integer, ForeignKey('authorInfos.id')) # Many-to-One with authorInfo
authorInfosLink = relationship('AuthorInfo', back_populates='threadInfosLink') # Many-to-One with authorInfo
class PostInfo(Base):
__tablename__ = 'postInfos'
id = Column(Integer, primary_key=True)
postOrder = Column('postOrder', Integer, nullable=True)
postAuthor = Column('postAuthor', Text(), nullable=True)
postContent = Column('postContent', Text(), nullable=True)
postTimestamp = Column('postTimestamp', Text(), nullable=True)
threadInfos_id = Column(Integer, ForeignKey('threadInfos.id')) # Many-to-One with threadInfo
threadInfosLink = relationship('ThreadInfo', back_populates='postInfosLink') # Many-to-One with threadInfo
authorInfos_id = Column(Integer, ForeignKey('authorInfos.id')) # Many-to-One with authorInfo
authorInfosLink = relationship('AuthorInfo', back_populates='postInfosLink') # Many-to-One with authorInfo
class AuthorInfo(Base):
__tablename__ = 'authorInfos'
id = Column(Integer, primary_key=True)
threadAuthor = Column('threadAuthor', String())
postInfosLink = relationship('PostInfo', back_populates='authorInfosLink') # One-to-Many with postInfo
threadInfosLink = relationship('ThreadInfo', back_populates='authorInfosLink') # One-to-Many with threadInfo
Ecco il mio pipelines.py
from sqlalchemy import exists, event
from sqlalchemy.orm import sessionmaker
from scrapy.exceptions import DropItem
from .models import db_connect, create_table, BoardInfo, ThreadInfo, PostInfo, AuthorInfo
from sqlalchemy.engine import Engine
from sqlite3 import Connection as SQLite3Connection
import logging
@event.listens_for(Engine, "connect")
def _set_sqlite_pragma(dbapi_connection, connection_record):
if isinstance(dbapi_connection, SQLite3Connection):
cursor = dbapi_connection.cursor()
cursor.execute("PRAGMA foreign_keys=ON;")
# print("@@@@@@@ PRAGMA prog is running!! @@@@@@")
cursor.close()
class DuplicatesPipeline(object):
def __init__(self):
'''
Initializes database connection and sessionmaker.
Creates tables.
'''
engine = db_connect()
create_table(engine)
self.Session = sessionmaker(bind=engine)
logging.info('****DuplicatesPipeline: database connected****')
def process_item(self, item, spider):
session = self.Session()
exist_threadLink = session.query(exists().where(ThreadInfo.threadLink == item['threadLink'])).scalar()
exist_thread_replyCount = session.query(ThreadInfo.replyCount).filter_by(threadLink = item['threadLink']).scalar()
if exist_threadLink is True: # threadLink is in DB
if exist_thread_replyCount < item['replyCount']: # check if replyCount is more?
return item
session.close()
else:
raise DropItem('Duplicated item found and replyCount is not changed')
session.close()
else: # New threadLink to be added to BoardPipeline
return item
session.close()
class BoardPipeline(object):
def __init__(self):
'''
Initializes database connection and sessionmaker
Creates tables
'''
engine = db_connect()
create_table(engine)
self.Session = sessionmaker(bind=engine)
def process_item(self, item, spider):
'''
Save scraped info in the database
This method is called for every item pipeline component
'''
session = self.Session()
# Input info to boardInfos
boardInfo = BoardInfo()
boardInfo.boardName = item['boardName']
# Input info to threadInfos
threadInfo = ThreadInfo()
threadInfo.threadTitle = item['threadTitle']
threadInfo.threadLink = item['threadLink']
threadInfo.threadAuthor = item['threadAuthor']
threadInfo.threadPost = item['threadPost']
threadInfo.replyCount = item['replyCount']
threadInfo.readCount = item['readCount']
# Input info to postInfos
# Due to info is in list, so we have to loop and add it.
for num in range(len(item['postOrder'])):
postInfoNum = 'postInfo' + str(num)
postInfoNum = PostInfo()
postInfoNum.postOrder = item['postOrder'][num]
postInfoNum.postAuthor = item['postAuthor'][num]
postInfoNum.postContent = item['postContent'][num]
postInfoNum.postTimestamp = item['postTimestamp'][num]
session.add(postInfoNum)
# Input info to authorInfo
authorInfo = AuthorInfo()
authorInfo.threadAuthor = item['threadAuthor']
# check whether the boardName exists
exist_boardName = session.query(exists().where(BoardInfo.boardName == item['boardName'])).scalar()
if exist_boardName is False: # the current boardName does not exists
session.add(boardInfo)
# check whether the threadAuthor exists
exist_threadAuthor = session.query(exists().where(AuthorInfo.threadAuthor == item['threadAuthor'])).scalar()
if exist_threadAuthor is False: # the current threadAuthor does not exists
session.add(authorInfo)
try:
session.add(threadInfo)
session.commit()
except:
session.rollback()
raise
finally:
session.close()
return item
Risposte
Dal codice che posso vedere, non mi sembra che tu stia ambientando ThreadInfo.authorInfosLink
o da ThreadInfo.authorInfos_id
nessuna parte (lo stesso vale per tutte le tue relazioni FK /).
Affinché gli oggetti correlati siano collegati a un'istanza ThreadInfo, è necessario crearli e quindi allegarli qualcosa come:
# Input info to authorInfo
authorInfo = AuthorInfo()
authorInfo.threadAuthor = item['threadAuthor']
threadInfo.authorInfosLink = authorInfo
Probabilmente non vuoi session.add () ogni oggetto se è correlato tramite FK. Ti consigliamo di:
- istanziare un
BoardInfo
oggettobi
- quindi istanzia allegare il tuo
ThreadInfo
oggetto correlatoti
- allega il tuo oggetto correlato es
bi.threadInfosLink = ti
- Alla fine di tutte le tue relazioni concatenate, puoi semplicemente aggiungere
bi
alla sessione usandosession.add(bi)
: tutti gli oggetti correlati verranno aggiunti attraverso le loro relazioni e gli FK saranno corretti.
Secondo la discussione nei commenti dell'altra mia risposta, di seguito è riportato come razionalizzerei i tuoi modelli per renderli più sensati per me.
Avviso:
- Ho rimosso le "Info" non necessarie ovunque
- Ho rimosso i nomi di colonna espliciti dalle definizioni del modello e mi baserò invece sulla capacità di SQLAlchemy di dedurli per me in base ai nomi degli attributi
- In un oggetto "Post" non nomino l'attributo PostContent, è implicito che il contenuto sia correlato al Post perché è così che vi accediamo - invece chiama semplicemente l'attributo "Post"
- Ho rimosso tutta la terminologia "Link" - nei punti in cui penso che tu voglia un riferimento a una raccolta di oggetti correlati, ho fornito un attributo plurale di quell'oggetto come relazione.
- Ho lasciato una riga nel modello Post da rimuovere. Come puoi vedere, non hai bisogno di "autore" due volte - una volta come oggetto correlato e una volta sul Post, questo vanifica lo scopo degli FK.
Con queste modifiche, quando si tenta di utilizzare questi modelli dall'altro codice, diventa ovvio dove è necessario utilizzare .append () e dove si assegna semplicemente l'oggetto correlato. Per un dato oggetto Board sai che 'thread' è una raccolta basata solo sul nome dell'attributo, quindi farai qualcosa comeb.threads.append(thread)
from sqlalchemy import create_engine, Column, Table, ForeignKey, MetaData
from sqlalchemy import Integer, String, Date, DateTime, Float, Boolean, Text
from sqlalchemy.orm import relationship
from sqlalchemy.ext.declarative import declarative_base
class Board(Base):
__tablename__ = 'board'
id = Column(Integer, primary_key=True)
name = Column(String(100))
threads = relationship(back_populates='board')
class Thread(Base):
__tablename__ = 'thread'
id = Column(Integer, primary_key=True)
title = Column(String())
link = Column(String())
author = Column(String())
post = Column(Text())
reply_count = Column(Integer)
read_count = Column(Integer)
board_id = Column(Integer, ForeignKey('Board.id'))
board = relationship('Board', back_populates='threads')
posts = relationship('Post', back_populates='threads')
author_id = Column(Integer, ForeignKey('Author.id'))
author = relationship('Author', back_populates='threads')
class Post(Base):
__tablename__ = 'post'
id = Column(Integer, primary_key=True)
order = Column(Integer, nullable=True)
author = Column(Text(), nullable=True) # remove this line and instead use the relationship below
content = Column(Text(), nullable=True)
timestamp = Column(Text(), nullable=True)
thread_id = Column(Integer, ForeignKey('Thread.id'))
thread = relationship('Thread', back_populates='posts')
author_id = Column(Integer, ForeignKey('Author.id'))
author = relationship('Author', back_populates='posts')
class AuthorInfo(Base):
__tablename__ = 'author'
id = Column(Integer, primary_key=True)
name = Column(String())
posts = relationship('Post', back_populates='author')
threads = relationship('Thread', back_populates='author')