Scrapy-SQLite에서 생성되지 않은 SQLalchemy 외래 키
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
답변
당신이 설정하는 것처럼 내가 볼 수있는 코드에서, 그것은 나에게 보이지 않는 ThreadInfo.authorInfosLink또는 ThreadInfo.authorInfos_id(같은 당신의 FK / 모든 관계도 마찬가지) 어디.
관련 개체를 ThreadInfo 인스턴스에 연결하려면 개체를 만든 다음 다음과 같이 연결해야합니다.
# Input info to authorInfo
authorInfo = AuthorInfo()
authorInfo.threadAuthor = item['threadAuthor']
threadInfo.authorInfosLink = authorInfo
FK를 통해 관련된 경우 각 객체를 session.add ()하지 않을 것입니다. 다음을 원할 것입니다.
BoardInfo개체를 인스턴스화bi- 그런 다음 관련
ThreadInfo개체 를 인스턴스화하십시오.ti - 예를 들어 관련 개체를 첨부하십시오.
bi.threadInfosLink = ti - 모든 연결 관계가 끝나면 간단히 다음
bi을 사용하여 세션에 추가 할 수 있습니다.session.add(bi)모든 관련 개체가 관계를 통해 추가되고 FK가 올 바릅니다.
내 다른 답변의 의견에 대한 토론에 따라 아래에 모델을 합리화하여 나에게 더 이해하기 쉬운 방법이 있습니다.
주의:
- 불필요한 "정보"를 모든 곳에서 제거했습니다.
- 모델 정의에서 명시 적 열 이름을 제거했으며 대신 SQLAlchemy의 특성 이름을 기반으로 추론하는 기능에 의존합니다.
- "Post"개체에서 PostContent 속성의 이름을 지정하지 않습니다. 이는 우리가 액세스하는 방식이기 때문에 콘텐츠가 Post와 관련이 있음을 암시합니다. 대신 단순히 "Post"속성을 호출합니다.
- 모든 "링크"용어를 제거했습니다. 관련된 개체 모음에 대한 참조를 원한다고 생각하는 곳에서 해당 개체의 복수 속성을 관계로 제공했습니다.
- 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')