Scrapy - SQLalchemy Yabancı Anahtarı SQLite'da oluşturulmadı
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
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:
- bir
BoardInfonesneyi örneklemekbi - daha sonra ilgili
ThreadInfonesneyi ekleyinti - ilgili nesneyi iliştirin örneğin
bi.threadInfosLink = ti - Tüm zincirleme ilişkilerinizin sonunda,
bikullanarak oturuma ekleyebilirsinizsession.add(bi)- ilgili tüm nesneler ilişkileri yoluyla eklenecek ve FK'ler doğru olacaktır.
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:
- Gereksiz "Bilgi" yi her yerden kaldırdım
- 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
- 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
- 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.
- 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')