Scrapy-SQLite에서 생성되지 않은 SQLalchemy 외래 키

Aug 22 2020

itemLoader를 사용하여 Scrapy를 실행하여 모든 데이터를 수집하고 SQLite 3에 넣으려고했습니다. 원하는 모든 정보를 수집하는 데 성공했지만 외래 키 back_populates와 함께 사용하여 ThreadInfo 및 PostInfo 테이블에서 외래 키를 생성 할 수 없습니다 . 나는 시도 back_ref했지만 작동하지 않았습니다. 다른 모든 정보는 Scrapy가 완료된 후 SQLite 데이터베이스에 삽입되었습니다.

내 목표는 4 개의 테이블, boardInfo, threadInfo, postInfo 및 authorInfo가 서로 연결되어있는 것입니다.

  • boardInfo는 threadInfo와 일대 다 관계를 갖습니다.
  • threadInfo는 postInfo와 일대 다 관계를 갖습니다.
  • authorInfo는 threadInfo 및
    postInfo 와 일대 다 관계를 갖 습니다.

SQLite 용 DB Browser를 사용했고 내 외래 키의 값이 Null. 값 (threadInfo.boardInfos_id)에 대한 쿼리를 시도했는데 None. 이 문제를 며칠 동안 고치려고 노력하고 문서를 읽었지만 문제를 해결할 수 없습니다.

내 threadInfo 및 postInfo 테이블에서 외부 키를 생성하려면 어떻게해야합니까?

모든 지침과 의견에 감사드립니다.

여기 내 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

다음은 내 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

답변

kerasbaz Aug 22 2020 at 08:43

당신이 설정하는 것처럼 내가 볼 수있는 코드에서, 그것은 나에게 보이지 않는 ThreadInfo.authorInfosLink또는 ThreadInfo.authorInfos_id(같은 당신의 FK / 모든 관계도 마찬가지) 어디.

관련 개체를 ThreadInfo 인스턴스에 연결하려면 개체를 만든 다음 다음과 같이 연결해야합니다.

        # Input info to authorInfo
        authorInfo = AuthorInfo()
        authorInfo.threadAuthor = item['threadAuthor'] 
        
        threadInfo.authorInfosLink = authorInfo

FK를 통해 관련된 경우 각 객체를 session.add ()하지 않을 것입니다. 다음을 원할 것입니다.

  1. BoardInfo개체를 인스턴스화bi
  2. 그런 다음 관련 ThreadInfo개체 를 인스턴스화하십시오.ti
  3. 예를 들어 관련 개체를 첨부하십시오. bi.threadInfosLink = ti
  4. 모든 연결 관계가 끝나면 간단히 다음 bi을 사용하여 세션에 추가 할 수 있습니다. session.add(bi)모든 관련 개체가 관계를 통해 추가되고 FK가 올 바릅니다.
kerasbaz Aug 22 2020 at 10:27

내 다른 답변의 의견에 대한 토론에 따라 아래에 모델을 합리화하여 나에게 더 이해하기 쉬운 방법이 있습니다.

주의:

  1. 불필요한 "정보"를 모든 곳에서 제거했습니다.
  2. 모델 정의에서 명시 적 열 이름을 제거했으며 대신 SQLAlchemy의 특성 이름을 기반으로 추론하는 기능에 의존합니다.
  3. "Post"개체에서 PostContent 속성의 이름을 지정하지 않습니다. 이는 우리가 액세스하는 방식이기 때문에 콘텐츠가 Post와 관련이 있음을 암시합니다. 대신 단순히 "Post"속성을 호출합니다.
  4. 모든 "링크"용어를 제거했습니다. 관련된 개체 모음에 대한 참조를 원한다고 생각하는 곳에서 해당 개체의 복수 속성을 관계로 제공했습니다.
  5. Post 모델에 제거 할 줄을 남겼습니다. 보시다시피, "작성자"가 두 번 필요하지 않습니다. 한 번은 관련 객체로, 한 번은 Post에 올리면 FK의 목적이 무효화됩니다.

이러한 변경으로 인해 다른 코드에서 이러한 모델을 사용하려고하면 .append ()를 사용해야하는 위치와 관련 객체를 할당하는 위치가 명확 해집니다. 주어진 Board 객체에 대해 'threads'가 속성 이름을 기반으로 한 컬렉션이라는 것을 알고 있으므로 다음과 같이 할 것입니다.b.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')