V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX 提问指南
helloworld000
V2EX  ›  问与答

请问 scrapy 爬虫大佬 关于 spider 要在多个页面下跳转提取 item 不同的 field, yield 出去 最后存取最终 item 的问题

  •  
  •   helloworld000 · 2020-08-22 05:37:25 +08:00 · 983 次点击
    这是一个创建于 1590 天前的主题,其中的信息可能已经有所发展或是发生改变。

    第一次用 scrapy, 请多多指教.

    用一个爬 stackoverflow 用户数据的例子来举例说明, 我的 spider 主要核心逻辑是这样的:

    • 先到 user 页面拿到所有 users;
    • 然后到每个 user 页面拿到 user 信息, 放入 item, 并且提取出这个 user 的 top-10 posts
    • 然后去这 10 个 posts 的页面, 提取出这个 user post 的内容, 也放入 item 最终保存给 pipeline, 此时跳转 users 页面下一页

    详细步骤是下面这个 (前面 4 步都 ok 没问题, 主要问题在第 5 步)

    1. 从这个 url 开始 start_urls = ['https://stackoverflow.com/users']

    2. 第一个 parse 把这个页面所有 users 拿到, 然后一个 for loop 对每个 user 提取基本信息, 比如头像,用户名等, 放入 item 里, 然后在这个 for loop 里用 yield scrapy.Request() 进入每个 user 的页面, 并且把之前的 item 用 meta 也传了过去, 也写了一个 parseUserPage 的函数, 一起在 request 里 callback 了. 此外, 在这个 parse 函数 for loop 遍历 users 外面的最最下面, 还有一个跳转下一页的 yield scrapy.Request()

    3. 在那个 parseUserPage 的函数里, 提取用户的自我介绍这种信息, 放入 item 里, 再拿到这个用户页面里 top10 的 posts 的 urls, 因为想把所有 posts 信息都放在一起, 所以我先让这个 item 里['posts']这个 field 新建了一个空的 list, 然后再一个 for loop 对提取的 top10 的 posts 一一遍历. // 前面的步骤都没有任何问题, 都能在 pipelines.py 的 process_item()里用 logger 打印出 item 并且跳转到下一页面, 就是到了第三步再一次进行跳转到 post 页面的时候开始出幺蛾子了

    4. 对每一个 post 的 url 再一次用 yield scrapy.Request() 进入每个 post 的页面. 还是一样, 把之前的 item 用 meta 也传了过去 (此外, 我知道 meta 是用的 shallow copy, 而且我那个 post 用的是 list, 所以我改成了 deepcopy(item) ) 也写了一个对应的 parsePost 的函数用来 callback

    5. 在这个 parsePostPage 的函数里, 对这个问题回答页面, 我提取出所有对应的问题和答案, 找出和 user-id 对应的 答案或者问题信息抓取, 然后放入 item 里 post field 里, 因为用的是 list, 所以我用的是 append(), 找到后直接 break 这个 for loop, 然后这个 parsePostPage 函数结尾 yield 出最终 item

    其他信息都没问题, 比如用户名字和基本介绍这些, 问题就出现在最后处理 post 的时候, 按道理我是应该能把 top-10 个 posts 的内容都能放进我的 item 里 post 这个 field 里, 可为什么每次打印出来的结果只有个空 list, 或者只拿到 1 个 post 的内容???

    到底是什么原因造成的只能拿到一个空 list 或者只有一个 post?

    我测试的时候, 在 praseUserPage 都能拿到 10 个 top-posts 的 urls, 但是为什么对 top-10 posts 的那个 for loop 没有跑完就存取 item 了?

    我的尝试和猜想

    • 我是应该在 parseUserPage 的 for loop 遍历完所有 top posts 的下面去 yield item 吗? 试了一下, 这样前面很多 users 就是空的 list 什么 post 内容都没拿到, 感觉应该还是在最后一个拿到 item 所有信息的函数里 yield 出 item

    • 我试了一下在最后那个 parseUsePost 的函数里, 对一个问答页面的 post 进行 for 循环找到 user 的 post 并且将其提取出来 append 到 item 的 post 里的 list 后, 不 break 这个 for loop, 改成直接 yield item.

    这样 item 里就没有空 list 出现了, 但还是只能拿到一个或者 2 个 post 的情况.....在 parseUserPage 的函数里那个处理 top-10 的 for loop 没有把剩余的 9 个 posts 后面的遍历完...

    不太明白为什么会出现这个情况...希望 scrapy 大佬能解惑一下, 多谢!

    9 条回复    2020-08-22 09:05:53 +08:00
    helloworld000
        1
    helloworld000  
    OP
       2020-08-22 06:02:12 +08:00
    我又试了一下在最开始初始化 item 的时候就初始化这个 post 的 filed, 并且初始化为一个空的 list, 并且在第一次 yield 到 praseUserPage 的时候就 deepcopy 这个 item/


    最后在 pipeline 里的 process_item() 里打印出来的 logger 信息就是同一个 user 的 item 在不同时候被打印出来了 10 次, 并且每次都是带着不同的 post...其他信息都没有问题, 就这个 post 每次都不一样...而且只有一个, 应该是读取到了 10 个 posts, 但是为什么没有把最这 10 个 post 都存到一个 list 里去??


    如果我想把这 10 个 posts 都放到一个 list 里 (或者其他数据结构里?) 去要怎么办?
    helloworld000
        2
    helloworld000  
    OP
       2020-08-22 06:03:16 +08:00
    最后在 scrapy 写入数据库的时候, 难道不是应该在 pipeline 的 process_item 里一次把 item 里全部写入吗?
    ooh
        3
    ooh  
       2020-08-22 06:59:28 +08:00
    您第 4 步直接用 requests 库 for 循环请求这 10 个 post 完成后后把 item yield 到 pipeline 里面就是 10 个。
    helloworld000
        4
    helloworld000  
    OP
       2020-08-22 07:11:36 +08:00
    @ooh 感谢回复!

    您意思是我第 4 步在 parseUserPage 里面的时候不用 yield scrapy.Request() 跳转到另外 post 页面?

    而是直接用 python 的 request 库 在 for loop 分别去处理 10 个 post 的页面, 然后加到 item 里去, 然后还是在 parseUserPage 的最后 yield item?
    helloworld000
        5
    helloworld000  
    OP
       2020-08-22 07:53:51 +08:00
    @ooh 感谢...搞定了...
    ooh
        6
    ooh  
       2020-08-22 07:58:28 +08:00
    @helloworld000
    如果是我会先用一个 user spider 类似下面这样的把所有 user 先入库可以只保存 user_id nickname

    class UserSpider(CrawlSpider):
    name = 'user'
    allowed_domains = ['stackoverflow.com']
    start_urls = ['https://stackoverflow.com/users']
    rules = (
    Rule(LinkExtractor(allow=('/users?page=(\\d+)&tab=reputation&filter=week')), callback='parse_item', follow=True),
    )

    然后再用一个 question spider 从 users 里面生成用户页面的 start_urls 抓取用户详情页面 先把你要的用户信息 yield 到 pipeline 里面根据 item type 更新用户数据,再 foreach yield 请求 question 页面,用 user_id 来关联
    Kobayashi
        7
    Kobayashi  
       2020-08-22 08:58:57 +08:00 via Android
    把 user 和 post 拆成不同的 item,存储到数据库后再处理。不要再 scrapy 里用什么阻塞的 requests,异步优势全没了。
    helloworld000
        8
    helloworld000  
    OP
       2020-08-22 09:03:34 +08:00
    @ooh 感谢大神! 第一次用 scrapy 不太熟悉让你见笑了

    请问你说的用另外一个 spider (question spider)去提取前一个 spider (user spider) 生成的 urls, 再进行关联.

    这个关联是在哪一步做的?在 pipeline 写另外一个函数吗?





    另外, 不过我还是非常好奇为什么之前用 scrapy.request 不行? 是不是之前哪个地方 yield 顺序错了?
    helloworld000
        9
    helloworld000  
    OP
       2020-08-22 09:05:53 +08:00
    @Kobayashi 感谢回复, 请问拆成不同的 items, 比如我需要 top10 的 posts, 那么就是在 post 这个 item 里预留 10 个 field ? 能有好点的解决方法一个 filed 装下所有的 posts 吗? 我用 list 之前就是每次 yield item 都只能保存一个...
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2736 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 22ms · UTC 14:52 · PVG 22:52 · LAX 06:52 · JFK 09:52
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.