Scrapy - Clé étrangère SQLalchemy non créée dans SQLite
J'ai essayé d'exécuter Scrapy en utilisant itemLoader pour collecter toutes les données et les mettre dans SQLite 3. J'ai réussi à rassembler toutes les informations que je voulais mais je ne peux pas obtenir les clés étrangères à générer dans mes tables ThreadInfo et PostInfo en utilisant back_populatesune clé étrangère. J'ai essayé back_refmais cela n'a pas fonctionné non plus. Toutes les autres informations ont été insérées dans la base de données SQLite après la fin de mon Scrapy.
Mon objectif est d'avoir quatre tables, boardInfo, threadInfo, postInfo et authorInfo liées les unes aux autres.
- boardInfo aura une relation un-à-plusieurs avec threadInfo
- threadInfo aura une relation un-à-plusieurs avec postInfo
- authorInfo aura une relation un-à-plusieurs avec threadInfo et
postInfo.
J'ai utilisé DB Browser pour SQLite et j'ai trouvé que les valeurs de mes clés étrangères sont Null. J'ai essayé une requête pour la valeur (threadInfo.boardInfos_id), et elle s'est affichée None. J'essaie de résoudre ce problème pendant plusieurs jours et de lire le document, mais je ne peux pas résoudre le problème.
Comment puis-je faire générer les clés foriegn dans mes tables threadInfo et postInfo?
Merci pour tous les conseils et commentaires.
Voici mes 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
Voici mes 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
Réponses
D'après le code que je peux voir, il ne me semble pas que vous définissiez ThreadInfo.authorInfosLinkou ThreadInfo.authorInfos_idnulle part (il en va de même pour tous vos FK / relations).
Pour que les objets associés soient attachés à une instance ThreadInfo, vous devez les créer, puis les attacher quelque chose comme:
# Input info to authorInfo
authorInfo = AuthorInfo()
authorInfo.threadAuthor = item['threadAuthor']
threadInfo.authorInfosLink = authorInfo
Vous ne souhaitez probablement pas session.add () chaque objet s'il est lié via FK. Vous voudrez:
- instancier un
BoardInfoobjetbi - puis instanciez attachez votre
ThreadInfoobjet associéti - attachez votre objet associé, par exemple
bi.threadInfosLink = ti - À la fin de toutes vos relations chaînées, vous pouvez simplement ajouter
bià la session en utilisantsession.add(bi)- tous les objets liés seront ajoutés via leurs relations et les FK seront corrects.
D'après la discussion dans les commentaires de mon autre réponse, voici comment je rationaliserais vos modèles pour qu'ils aient plus de sens pour moi.
Remarquer:
- J'ai supprimé partout les "Info" inutiles
- J'ai supprimé les noms de colonnes explicites de vos définitions de modèle et je vais plutôt me fier à la capacité de SQLAlchemy à les déduire pour moi en fonction de mes noms d'attributs
- Dans un objet "Post", je ne nomme pas l'attribut PostContent, cela implique que le contenu se rapporte à la publication car c'est ainsi que nous y accédons - appelez simplement l'attribut "Post"
- J'ai supprimé toute la terminologie "Lien" - dans les endroits où je pense que vous voulez une référence à une collection d'objets liés, j'ai fourni un attribut pluriel de cet objet comme relation.
- J'ai laissé une ligne dans le modèle de publication que vous pouvez supprimer. Comme vous pouvez le voir, vous n'avez pas besoin d '"auteur" deux fois - une fois en tant qu'objet lié et une fois sur le poste, ce qui va à l'encontre de l'objectif des FK.
Avec ces modifications, lorsque vous essayez d'utiliser ces modèles à partir de votre autre code, il devient évident où vous devez utiliser .append () et où vous affectez simplement l'objet associé. Pour un objet Board donné, vous savez que 'threads' est une collection basée uniquement sur le nom de l'attribut, vous allez donc faire quelque chose commeb.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')