Scrapy - SQLalchemy Yabancı Anahtarı SQLite'da oluşturulmadı

Aug 22 2020

Scrapy'yi tüm verileri toplamak ve SQLite 3'e koymak için itemLoader kullanarak çalıştırmayı denedim. İstediğim tüm bilgileri toplamada başarılı oldum ancak ThreadInfo ve PostInfo tablolarımda back_populatesyabancı anahtar kullanarak üretilecek yabancı anahtarları alamıyorum . Denedim back_refama işe yaramadı. Diğer tüm bilgiler, Scrapy'im bittikten sonra SQLite veritabanına eklendi.

Amacım birbirine bağlı dört tablonun, boardInfo, threadInfo, postInfo ve authorInfo'ya sahip olmaktır.

  • boardInfo, threadInfo ile bire çok ilişkisine sahip olacaktır
  • threadInfo, postInfo ile bire çok ilişkisine sahip olacaktır
  • AuthorInfo, threadInfo ve
    postInfo ile bire çok ilişkisine sahip olacaktır.

SQLite için DB Browser kullandım ve yabancı anahtarlarımın değerlerinin olduğunu buldum Null. (ThreadInfo.boardInfos_id) değerini sorgulamayı denedim ve görüntülendi None. Bunu günlerce düzeltmeye çalışıyorum ve belgeyi okudum ancak sorunu çözemiyorum.

ThreadInfo ve postInfo tablolarımda oluşturulan foriegn anahtarlarına nasıl sahip olabilirim?

Tüm rehberlik ve yorumlarınız için teşekkür ederiz.

İşte modellerim.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

İşte ardışık düzenlerim.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

Yanıtlar

kerasbaz Aug 22 2020 at 08:43

Gördüğüm koddan, bana sizin ayarladığınız gibi ThreadInfo.authorInfosLinkveya ThreadInfo.authorInfos_idherhangi bir yer gibi görünmüyor (aynı şey tüm FK / ilişkileriniz için de geçerli).

İlgili nesnelerin bir ThreadInfo örneğine eklenmesi için, bunları oluşturmanız ve ardından aşağıdakilere benzer bir şey eklemeniz gerekir:

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

FK ile ilişkiliyse muhtemelen her nesneyi session.add () yapmak istemezsiniz. Şunları yapmak isteyeceksiniz:

  1. bir BoardInfonesneyi örneklemekbi
  2. daha sonra ilgili ThreadInfonesneyi ekleyinti
  3. ilgili nesneyi iliştirin örneğin bi.threadInfosLink = ti
  4. Tüm zincirleme ilişkilerinizin sonunda, bikullanarak oturuma ekleyebilirsiniz session.add(bi)- ilgili tüm nesneler ilişkileri yoluyla eklenecek ve FK'ler doğru olacaktır.
kerasbaz Aug 22 2020 at 10:27

Diğer cevabımın yorumlarındaki tartışmaya göre, aşağıda modellerinizi bana daha anlamlı hale getirmek için nasıl rasyonelleştireceğim.

Farkına varmak:

  1. Gereksiz "Bilgi" yi her yerden kaldırdım
  2. Model tanımlarınızdan açık sütun adlarını kaldırdım ve bunun yerine SQLAlchemy'nin bunları benim için öznitelik adlarıma dayalı olarak çıkarabilme becerisine güveneceğim
  3. Bir "Gönderi" nesnesinde PostContent niteliğini adlandırmıyorum, içeriğin Gönderi ile ilgili olduğu ima ediliyor çünkü ona bu şekilde erişiyoruz - bunun yerine sadece "Gönderi" özelliğini çağırın
  4. Tüm "Bağlantı" terminolojisini kaldırdım - ilgili nesnelerin bir koleksiyonuna referans istediğinizi düşündüğüm yerlerde, ilişki olarak o nesnenin çoğul bir özelliğini sağladım.
  5. Post modelinde kaldırmanız için bir satır bıraktım. Gördüğünüz gibi, "yazar" a iki kez ihtiyacınız yok - bir kez ilgili nesne olarak ve bir kez de FK'lerin amacını bozan Post'ta.

Bu değişikliklerle, bu modelleri diğer kodunuzdan kullanmaya çalıştığınızda, .append () 'i nerede kullanmanız gerektiği ve ilgili nesneyi nereye atadığınız belli olur. Belirli bir Board nesnesi için, 'konuların' sadece öznitelik adına dayalı bir koleksiyon olduğunu bilirsiniz, bu nedenle, şöyle bir şey yapacaksınız: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')