添加反向关系 User.contents = relationship(“WebContent“, order_by=WebContent.id, back_populates=“user“)

示例代码(爬虫):

import logging
from typing import Optional

# 设置日志
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)

# 尝试导入必要的库
try:
    import requests
    from bs4 import BeautifulSoup
    from sqlalchemy import create_engine, Column, Integer, String, Text, ForeignKey
    from sqlalchemy.ext.declarative import declarative_base
    from sqlalchemy.orm import sessionmaker, relationship
except ImportError:
    logger.error("请安装必要的库: pip install requests beautifulsoup4 sqlalchemy")
    exit(1)

# 创建数据库模型
Base = declarative_base()

class User(Base):
    __tablename__ = 'users'
    
    id = Column(Integer, primary_key=True)
    full_name = Column(String(100), unique=True, nullable=False)
    
    def __repr__(self):
        return f"<User(id={self.id}, full_name='{self.full_name}')>"

class WebContent(Base):
    __tablename__ = 'web_contents'
    
    id = Column(Integer, primary_key=True)
    user_id = Column(Integer, ForeignKey('users.id'), nullable=False)
    url = Column(String(500), nullable=False)
    title = Column(String(200))
    content = Column(Text)
    
    user = relationship("User", back_populates="contents")
    
    def __repr__(self):
        return f"<WebContent(id={self.id}, url='{self.url}', title='{self.title}')>"

# 添加反向关系
User.contents = relationship("WebContent", order_by=WebContent.id, back_populates="user")

# 数据库连接和会话
def get_db_session():
    """创建数据库连接和会话"""
    # 使用SQLite作为数据库
    engine = create_engine('sqlite:///web_crawler.db')
    Base.metadata.create_all(engine)
    Session = sessionmaker(bind=engine)
    return Session()

def get_or_create_user(user_full_name: str) -> User:
    """
    获取或创建用户
    
    Args:
        user_full_name: 用户全名
        
    Returns:
        User: 用户对象
    """
    session = get_db_session()
    
    # 查找用户,如果不存在则创建
    user = session.query(User).filter(User.full_name == user_full_name).first()
    if not user:
        user = User(full_name=user_full_name)
        session.add(user)
        session.commit()
        logger.info(f"创建新用户: {user_full_name}")
    else:
        logger.info(f"找到现有用户: {user_full_name}")
    
    return user

def crawl_webpage(user: User, url: str) -> Optional[WebContent]:
    """
    爬取单个网页并存储内容
    
    Args:
        user: 用户对象
        url: 要爬取的URL
        
    Returns:
        WebContent: 爬取的网页内容对象
    """
    session = get_db_session()
    
    # 检查URL是否已经爬取过
    existing_content = session.query(WebContent).filter(
        WebContent.user_id == user.id,
        WebContent.url == url
    ).first()
    
    if existing_content:
        logger.info(f"URL已经爬取过: {url}")
        return existing_content
    
    # 爬取网页
    logger.info(f"开始爬取URL: {url}")
    try:
        response = requests.get(url, timeout=30)
        response.raise_for_status()  # 如果请求失败则抛出异常
        
        # 解析HTML
        soup = BeautifulSoup(response.text, 'html.parser')
        
        # 提取标题和内容
        title = soup.title.string if soup.title else "无标题"
        
        # 提取正文内容 (这里简化处理,实际应用中可能需要更复杂的提取逻辑)
        paragraphs = soup.find_all('p')
        content = "\n".join([p.get_text() for p in paragraphs])
        
        # 存储到数据库
        web_content = WebContent(
            user_id=user.id,
            url=url,
            title=title,
            content=content
        )
        
        session.add(web_content)
        session.commit()
        
        logger.info(f"成功爬取并存储URL: {url}, 标题: {title}")
        return web_content
        
    except Exception as e:
        logger.error(f"爬取URL时出错: {url}, 错误: {str(e)}")
        return None

def main():
    """主函数"""
    # 用户名
    user_full_name = "Paul Iusztin"
    
    # 要爬取的URL (只爬取一个)
    url = "https://mlabonne.github.io/blog/posts/2024-07-29_Finetune_Llama31.html"  # 请替换为你想爬取的实际URL
    
    # 获取或创建用户
    user = get_or_create_user(user_full_name)
    
    # 爬取网页
    web_content = crawl_webpage(user, url)
    logger.info(f"web_content===: {web_content}")
    if web_content:
        logger.info(f"爬取成功! 标题: {web_content.title}")
        logger.info(f"内容预览: {web_content.content[:200]}...")
    else:
        logger.error("爬取失败!")

if __name__ == "__main__":
    main()

这段代码给 User 模型添加了一个属性,叫做 contents,用来表示与该用户关联的所有 WebContent 对象。具体来说,它使用了 SQLAlchemy 提供的 relationship() 函数,语法规则和功能说明如下:

  1. 基本语法说明

    • User.contents = relationship("WebContent", ...)
      这是在给 User 类“动态”添加一个属性(contents),这个属性的值在查询时会返回所有与这个用户相关联的 WebContent 对象。
    • "WebContent":表示关联的目标模型是 WebContent(注意这里是字符串,SQLAlchemy 会延迟解析)。
    • order_by=WebContent.id:指明在获取关联数据时,以 WebContent 表中的 id 字段进行排序(默认是升序)。
    • back_populates="user":表示在 WebContent 模型中,存在一个属性 user,与 User.contents 形成双向关联。也就是说,当你从 WebContent 访问 user 时,得到的就是与之关联的 User 对象,而从 User 访问 contents 时,得到的是该用户所有相关的 WebContent 对象。
  2. 通俗讲
    想象你有一个图书馆系统,User 就像借书人,而 WebContent 就像他们借的书。

    • User.contents 就相当于在借书人档案中记录“借书清单”。
    • 当你查看某个借书人的档案(User 对象)时,通过 .contents 属性可以看到他借了哪些书(WebContent 对象),而且这些书是按照书的编号(这里用的是 id)排序的。
    • 同时,每本书记录中都有一个“借书人”的字段(在 WebContent 模型中,通过 back_populates=“user” 指定),这样你可以从书反查到具体是谁借的。
  3. 数值详细举例说明

    假设我们有如下数据记录:

    • 用户(User)记录

      • 用户 A:
        • id = 1
        • full_name = “Alice”
    • 网页内容(WebContent)记录
      假设 Alice 关联了三个网页内容:

      • 网页内容 1:
        • id = 10
        • user_id = 1(表示属于 Alice)
        • title = “Page One”
      • 网页内容 2:
        • id = 5
        • user_id = 1
        • title = “Page Two”
      • 网页内容 3:
        • id = 20
        • user_id = 1
        • title = “Page Three”

    当你查询用户 Alice 的 contents 属性时,由于我们设置了 order_by=WebContent.id,返回的列表会根据 id 从小到大排序,结果就是:

    alice.contents  # 返回的顺序为:[网页内容 2 (id=5), 网页内容 1 (id=10), 网页内容 3 (id=20)]
    

    此外,在 WebContent 的每个对象中,由于设置了 back_populates="user",你可以通过访问:

    some_webcontent.user  # 这将返回对应的用户对象,也就是 Alice
    

总的来说,这行代码就是在建立一种双向关系,让你可以非常方便地从用户对象访问到该用户对应的所有网页内容(并按 id 排序),同时也能从每个网页内容反查到所属的用户。

同时,这种对应关系主要依赖于两个方面:

  1. 外键(ForeignKey)关系
    在 WebContent 模型中,有一个字段 user_id,它通过 ForeignKey('users.id') 指定了与 User 表中的 id 字段相关联。这就告诉 SQLAlchemy,每条 WebContent 记录的 user_id 对应的是 User 表中某个用户的 id,也就是说,当 WebContent 的 user_id 为 1 时,它就关联到 User 表中 id 为 1 的用户。

  2. 双向关联的声明(back_populates)
    在 WebContent 模型中,我们写了:

    user = relationship("User", back_populates="contents")
    

    而在 User 模型中,通过:

    User.contents = relationship("WebContent", order_by=WebContent.id, back_populates="user")
    

    双方互相声明了对方的属性名称。这意味着:

    • 当你通过 user.contents 访问时,SQLAlchemy 会自动查找所有 WebContent 记录,其 user_id 与该用户的 id 匹配;
    • 当你从某个 WebContent 记录访问 web_content.user 时,SQLAlchemy 就会返回对应的 User 对象。

这两部分协同工作,SQLAlchemy 根据 WebContent 中的外键 user_id 和双方在 relationship 中设置的 back_populates 属性,自动确定每个用户和网页内容之间的对应关系。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值