Scrapy - SQLalchemy Foreign Key ไม่ได้สร้างใน SQLite
ฉันพยายามเรียกใช้ Scrapy โดยใช้ itemLoader เพื่อรวบรวมข้อมูลทั้งหมดและใส่ลงใน SQLite 3 ฉันประสบความสำเร็จในการรวบรวมข้อมูลทั้งหมดที่ฉันต้องการ แต่ฉันไม่สามารถสร้างคีย์ต่างประเทศในตาราง ThreadInfo และ PostInfo ของฉันโดยใช้back_populatesกับ Foreign Key ฉันลองแล้วback_refแต่ก็ไม่ได้ผล ข้อมูลอื่น ๆ ทั้งหมดถูกแทรกลงในฐานข้อมูล SQLite หลังจาก Scrapy ของฉันเสร็จสิ้น
เป้าหมายของฉันคือมีสี่ตาราง boardInfo, threadInfo, postInfo และ authorInfo ที่เชื่อมโยงกัน
- boardInfo จะมีความสัมพันธ์แบบหนึ่งต่อกลุ่มกับ threadInfo
- threadInfo จะมีความสัมพันธ์แบบหนึ่งต่อกลุ่มกับ postInfo
- authorInfo จะมีความสัมพันธ์แบบหนึ่งต่อกลุ่มกับ threadInfo และ
postInfo
ผมใช้ DB เบราว์เซอร์สำหรับ SQLite Nullและพบว่าค่าของคีย์ต่างประเทศของฉันที่มี ฉันลองค้นหาค่า (threadInfo.boardInfos_id) แล้วมันก็ปรากฏNoneขึ้น ฉันพยายามแก้ไขปัญหานี้มาหลายวันและอ่านเอกสาร แต่ไม่สามารถแก้ปัญหาได้
ฉันจะสร้างคีย์ foriegn ในตาราง 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
คุณอาจไม่ต้องการ session.add () แต่ละออบเจ็กต์หากเกี่ยวข้องกันผ่าน FK คุณจะต้อง:
- สร้างอินสแตนซ์
BoardInfoวัตถุbi - จากนั้นสร้างอินสแตนซ์แนบ
ThreadInfoวัตถุที่เกี่ยวข้องของคุณti - แนบวัตถุที่เกี่ยวข้องของคุณเช่น
bi.threadInfosLink = ti - ในตอนท้ายของความสัมพันธ์ที่ถูกล่ามโซ่ของคุณคุณสามารถเพิ่มลง
biในเซสชันโดยใช้session.add(bi)- วัตถุที่เกี่ยวข้องทั้งหมดจะถูกเพิ่มผ่านความสัมพันธ์และ FK จะถูกต้อง
ตามการอภิปรายในความคิดเห็นของคำตอบอื่น ๆ ของฉันด้านล่างนี้คือวิธีที่ฉันจะหาเหตุผลเข้าข้างตนเองแบบจำลองของคุณเพื่อให้เหมาะสมกับฉันมากขึ้น
ข้อสังเกต:
- ฉันได้ลบ "ข้อมูล" ที่ไม่จำเป็นออกทุกที่
- ฉันได้ลบชื่อคอลัมน์ที่ชัดเจนออกจากคำจำกัดความของโมเดลของคุณแล้วและจะใช้ความสามารถของ SQLAlchemy ในการอนุมานสิ่งเหล่านั้นให้ฉันตามชื่อแอตทริบิวต์
- ในออบเจ็กต์ "โพสต์" ฉันไม่ได้ตั้งชื่อแอตทริบิวต์ PostContent แต่โดยนัยว่าเนื้อหาเกี่ยวข้องกับโพสต์เพราะนั่นคือวิธีที่เรากำลังเข้าถึง - เพียงแค่เรียกแอตทริบิวต์ว่า "Post" แทน
- ฉันได้ลบคำศัพท์ "Link" ทั้งหมดแล้ว - ในที่ที่ฉันคิดว่าคุณต้องการอ้างอิงถึงคอลเล็กชันของอ็อบเจ็กต์ที่เกี่ยวข้องซึ่งฉันได้ระบุแอตทริบิวต์พหูพจน์ของวัตถุนั้นเป็นความสัมพันธ์
- ฉันได้ทิ้งบรรทัดไว้ในโมเดลโพสต์เพื่อให้คุณลบ อย่างที่คุณเห็นคุณไม่จำเป็นต้องมี "ผู้เขียน" สองครั้ง - ครั้งหนึ่งเป็นวัตถุที่เกี่ยวข้องและอีกครั้งในโพสต์ที่เอาชนะวัตถุประสงค์ของ FKs
ด้วยการเปลี่ยนแปลงเหล่านี้เมื่อคุณพยายามใช้โมเดลเหล่านี้จากโค้ดอื่น ๆ ของคุณจะเห็นได้ชัดว่าคุณต้องใช้. 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')