V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
solider245
V2EX  ›  Python

想爬取一个有很多页的网站,但是我不知道这个网站的准确页数。请问 Python 中如何用循环来实现呢?

  •  
  •   solider245 · 2019-07-27 13:20:26 +08:00 · 5545 次点击
    这是一个创建于 1979 天前的主题,其中的信息可能已经有所发展或是发生改变。

    如题,假如网址是

    www.baidu.com/page_01
    www.baidu.com/page_02
    www.baidu.com/page_03
    www.baidu.com/page_0{i}
    
    

    字母 i 代表页数,以前我爬取的网站,i 的数字是明确的,一般是 100,200,或者 300 以内。 这个时候我可以用 range(1,300)这样生成循环数来搞定。

    现在有一个网站,这个自增数量太大,我应该如何用条件判断和循环来解决这个问题呢?

    41 条回复    2019-07-28 16:18:46 +08:00
    wzwwzw
        1
    wzwwzw  
       2019-07-27 13:38:05 +08:00
    scrapy rule .
    xiaoming1992
        2
    xiaoming1992  
       2019-07-27 13:39:54 +08:00
    设置一个尽可能大的值,循环过程中进行判断,没爬到希望的内容就跳出循环并通知你,你再人肉看看后面还有没有,再进行下一步操作?
    limuyan44
        3
    limuyan44  
       2019-07-27 13:40:04 +08:00
    这种算最简单的爬取规则了吧你一直爬到没有不就好了。
    ranleng
        4
    ranleng  
       2019-07-27 13:40:26 +08:00 via Android
    while true。
    然后 404 的时候 break 掉
    lihongjie0209
        6
    lihongjie0209  
       2019-07-27 13:46:13 +08:00
    while 不行啊
    solider245
        7
    solider245  
    OP
       2019-07-27 13:49:01 +08:00
    @ranleng #4 请问可以稍微写一个例子吗?
    solider245
        8
    solider245  
    OP
       2019-07-27 13:49:59 +08:00
    @xiaoming1992 #2 设置最大值会遇到一种情况,就是以后当网站的页数超过你的最大值时,你的爬虫就要去更新了
    solider245
        9
    solider245  
    OP
       2019-07-27 13:50:11 +08:00
    @wzwwzw #5 谢谢,我去看下
    xiaoming1992
        10
    xiaoming1992  
       2019-07-27 13:54:34 +08:00 via Android
    本来就是啊,所以说尽可能大啊,比方说一百万,不够就设个三十亿(我怎么会说是我忘了 while true 呢)。

    另外,爬虫肯定要不定期更新的啊,因为人家也会更新反爬规则啊。
    cherbim
        11
    cherbim  
       2019-07-27 13:58:16 +08:00 via iPhone
    while ture,然后判断获取 http 的状态码,是 200 就继续循环,不是就跳出循环
    solider245
        12
    solider245  
    OP
       2019-07-27 14:18:28 +08:00
    @xiaoming1992 #10 我其实已经说了,这个设置最大值这块其实我已经会了。
    感觉看别人的代码,似乎有三种写法,if/else,try/expect,while ture 这个例子我还真没见过。
    solider245
        13
    solider245  
    OP
       2019-07-27 14:18:38 +08:00
    @cherbim #11 能大概写个小例子吗?
    cherbim
        14
    cherbim  
       2019-07-27 14:26:49 +08:00   ❤️ 1
    @solider245 当然可以啊,比如查询从你发布的帖子到现在发布了多少新帖
    代码如下:
    import requests

    url = r"https://www.v2ex.com/t/"
    header = {
    "User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
    }
    i = 586668
    while True:
    page_url = url + str(i)
    print(page_url)
    r = requests.get(page_url, header)
    if r.status_code == 200:
    i = i + 1
    else:
    break
    print("自楼主发帖起一共更新了" + str(i - 586668 - 1) + "贴")
    cherbim
        15
    cherbim  
       2019-07-27 14:27:50 +08:00
    我靠,这破站真是“小气鬼”,我就查了几次,把我 IP ban 了
    cherbim
        16
    cherbim  
       2019-07-27 14:34:18 +08:00
    loading
        17
    loading  
       2019-07-27 14:48:47 +08:00   ❤️ 1
    我用人工都能很快地尝试出来。
    二分法。
    solider245
        18
    solider245  
    OP
       2019-07-27 14:49:41 +08:00
    @cherbim #14 原来是这个原理,好的,谢谢哈,我按照这个逻辑,尝试一下
    JasonEWNL
        19
    JasonEWNL  
       2019-07-27 14:56:59 +08:00 via iPad
    比如你找到当前页的 “下一页” 特征(标签啊各种,一般靠 re 扒出来)定义为 next_url 啥的,然后在 while True 循环里 if next_url 就行了,只要一直有就会一直爬,没有就终止了。(以上来自以前爬多页漫画网站的经验)
    dongyx
        20
    dongyx  
       2019-07-27 16:26:33 +08:00   ❤️ 1
    楼主,这个可以用二分搜索解决啊。
    dongyx
        21
    dongyx  
       2019-07-27 17:15:13 +08:00
    我给楼主写一个实现,借助 bisect 标准模块,我们连二分搜索都不用自己实现,bisect_left()会求一个升序数组的 lower_bound,所以我们只需要写一个小类,覆盖__getitem__模拟数组行为,每次取下标 i 都去读站点的第 i 页,如果页面合法就返回 0,否则返回 1,然后调用 bisect_left 就可以了。

    下面的例子是二分搜索探寻 Hacker news 的新闻版 ( https://news.ycombinator.com/news?p={page}) 的最大页码。对于 Hacker News 来说,一个页面有帖子当且仅当 HTML 中含有字符串'class='title'",所以可以以这个标准来确定页面是否合法。

    V2EX 的回复似乎会忽略缩进,所以我也贴一个 gist: https://gist.github.com/dongyx/cdb7df063c6a4825a51571bd429d0157

    import requests
    import bisect

    def valid(resp):
    return int('class="title"' in resp.text)

    class PageStatus:
    def __getitem__(self, page):
    r = requests.get("https://news.ycombinator.com/news",
    params={"p": page})

    return 0 if valid(r) else 1

    end = bisect.bisect_left(PageStatus(), 1, lo=1, hi=1024)

    print("the page range is [1, {0})".format(end))
    solider245
        22
    solider245  
    OP
       2019-07-27 17:16:14 +08:00
    @dongyx #20 二分查找是什么意思?
    jugelizi
        23
    jugelizi  
       2019-07-27 17:23:13 +08:00
    话说 这个代码上来说二分是最简便的算法啊
    solider245
        24
    solider245  
    OP
       2019-07-27 17:25:15 +08:00
    @dongyx #21 你这个好像有点复杂啊,我得消化下。想问下,你这种写法和前面朋友给的 while True 方式相比,有哪些优点吗?
    因为 while true 我现在可以理解,而且也可以写出来了。
    dongyx
        25
    dongyx  
       2019-07-27 17:28:21 +08:00   ❤️ 1
    @solider245 就是一般意义上我们说的二分查找,一般的程序员应该都了解的。 如果你这方面有缺失,我时间不多,只能在这里简单解释一下:假设我有一个有序的整数数组,我想要查询里面一个数的下标,我先拿数组中间的数和目标数对比,如果相等那就找到了,如果小于目标值,就说明目标值在数组的后半部分,那我就去后半部分继续二分,如果大于目标值,说明目标值在数组的前半部分,我就去前部分用同样的方法找。

    这样不断地分割数组,我只需要正比于数组长度的对数的时间就能找到值,也就是 1024 长度的数组我只需要找 10 次左右。

    对于你的问题,你可以把所有页面看成是一个数组,合法是 0,不合法是 1,这个数组就是[0,0,0,....,1,1,1,1]这样的形式,你要找到最右边的 0,二分搜索就可以了。二分搜索的实现是很容易出 bug 的(除非你很好地掌握了循环不变式的思想, 但是如果你不知道二分搜索,我估计你也不知道循环不变式),所以不建议自己实现。bisect 标准模块已经实现了数组的二分搜索,所以你只需要构造这么一个数组,但是你不能真的去构造数组,不然相当于每个页面都去读取了一次,所以你可以覆盖__getitem__方法,构造一个假的数组,每次读数组的第 i 个值,你去取第 i 个页面就行了。

    我给的代码只是简单的 demo,要达到工业强度,还需要一些改进,比如请求失败的处理之类的,祝好运。

    PS:while 的做法是每个页码都尝试去读一次,比二分搜索慢了很多,但是如果你的性能需求没有你帖子里说的那么高,那就怎么简单怎么来吧。
    solider245
        26
    solider245  
    OP
       2019-07-27 17:43:27 +08:00
    @dongyx #25 哦,有点理解了。
    比如我有一个要爬取的网站,他的最大页面值等于 1W,但是我并不知道。
    如果用 while true 的话,我相当于要依次载入 9999 次,最终到达一万次,然后 10001 次的时候,页面返回的不是 200,然后这个循环就中止了。

    用二分的话,速度就要快一点?

    可问题是我的目的是爬取,求最大值只是为了让我的爬虫知道要爬多少个页面,这样的话,二分法用在这里,似乎并没有大的作用?除了可以让我的爬虫可以更快的获取网页的数量。

    不过感觉可以用来作为前置使用。比如我想爬取一下这个网站,然后我通过二分法,快速知道这个网站大概有多少个页面,这样的话,心里会更有数?
    xuanbg
        27
    xuanbg  
       2019-07-27 17:46:46 +08:00
    写个死循环,条件判断跳出不就好了。。。
    dongyx
        28
    dongyx  
       2019-07-27 17:47:31 +08:00
    @solider245 抱歉,我以为你的需求是确定最大页码,如果你是要爬去所有页面的话,用 while 就行了,二分搜索多此一举。
    daozhihun
        29
    daozhihun  
       2019-07-27 17:49:23 +08:00   ❤️ 5
    如果你要爬取所有页面,就写个死循环,一直到 404 或者没东西返回的时候再 break。
    如果你只需要确定页数,就可以先倍增再二分,比如 1、2、4、8、16 …… 假如到 1024 的时候没东西,那么页数就在 512-1024 之间,然后在这个区间二分。
    不过你要注意人家可能会反爬,甚至会投毒,这个就得你自己处理了。说不定人家发现你是爬虫,会返回假数据让你永远也爬不完,所以你要监控你的数据。
    反正做爬虫就要准备打持久战,小网站还好,大网站的反爬很恶心,你得做好每天都改的准备。
    dongyx
        30
    dongyx  
       2019-07-27 17:51:17 +08:00
    @daozhihun 倍增确定上界好赞,送一个感谢给你
    solider245
        31
    solider245  
    OP
       2019-07-27 18:02:36 +08:00
    @daozhihun #29 谢谢了,又涨知识了。
    solider245
        32
    solider245  
    OP
       2019-07-27 18:03:06 +08:00
    @xuanbg #27 让我很尴尬的是,网上几乎所有的教程都没有涉及到这方面。看来是这个问题太简单了
    zgl263885
        33
    zgl263885  
       2019-07-27 21:24:25 +08:00 via iPhone
    类似递归二分法定位问题,先随便给个比较大的数值,确认无效后开始递归二分法查找边界值。
    cherbim
        34
    cherbim  
       2019-07-27 22:28:45 +08:00
    @solider245 我给出代码的判断条件是是否 404,万一某个网站不给 404,直接给你跳转某个网页,那这代码就陷入死循环,就没用了,重新回到你的问题,你不知道一个网页有多少页,为什么要靠猜有多少页,为啥不让代码看一下网页有多少页,建议了解一下 xlml 这个库
    顺带给个范例,查询本贴共有多少人回复:
    import requests
    from lxml import etree

    url = r"https://www.v2ex.com/t/586668"
    header = {
    "User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
    }

    html = requests.get(url, headers=header)
    xml_content = etree.HTML(html.content)
    href_list = xml_content.xpath("//span[@class='no']/text()")
    print("当前回复最大楼层" + href_list[-1] + "楼")
    soho176
        35
    soho176  
       2019-07-27 22:59:57 +08:00
    http 的状态码 200
    solider245
        36
    solider245  
    OP
       2019-07-28 06:12:24 +08:00
    @cherbim #34 我去,还有这种套路啊?我都没遇到过,一般都是 200 或者非 200 的状态码。
    看来大家爬取的都是很高端的网站啊。我爬取的一般都是一些表格网站居多。
    谢谢你提供的范例,我琢磨和研究下。
    感觉上论坛问了之后,发现了以前很多根本没有接触到的东西。又触及到了我的知识盲区了。
    kppwp
        37
    kppwp  
       2019-07-28 08:56:18 +08:00 via iPhone
    是我没有理解楼主已经会设置最大页码的意思吗
    😴获取这个目录下的最大页码 源代码肯定有接口或者写死在标签里的
    这样直接循环遍历不挺好的么
    二分实在多此一举了
    kppwp
        38
    kppwp  
       2019-07-28 09:06:54 +08:00 via iPhone
    “但是我不知道这个网站的准确页数”
    如果真的是这样的话对用户太不友好了 设计再差的网站项目也不会忽略这点的 因此绝对有接口存在
    但是那种不打算对用户开放的站点可能不会考虑......但是如果是这种站点 他的入口又是哪里呢 从哪里搞到这个 url 的,从最初的地方去找逻辑

    总之这个不知道准确页数让我很迷惑
    应该就是知道的

    另外说一下
    判断不一定要用状态码啊 如果用 xpath 为空就让他为空好了 最后不会返回顺便判断一下

    二分我也有点迷惑 一样要遍历判断是否合法 二分完全是多余的
    Les1ie
        39
    Les1ie  
       2019-07-28 14:08:39 +08:00
    这种单纯确定页数一般不需要写代码 :)

    这种情况我一般是在浏览器手动二分试试最大的页数。楼上说的先倍增再二分,这个 tips 学到了 :) 比我直接瞎猜上界靠谱
    locoz
        40
    locoz  
       2019-07-28 14:29:15 +08:00 via Android
    最大页数未知的并不影响你爬啊,正常地一页一页翻下去不就好了吗?下一页给的哪个就跟着翻下去,拟人的操作不就是这样吗?
    还是说你想要知道所有的页码,直接并发请求列表页?没必要啊,你都说了是列表页了,主要影响速度的应该是在详情页上,跟列表页没啥关系。
    solider245
        41
    solider245  
    OP
       2019-07-28 16:18:46 +08:00
    @locoz #40 谢谢你的答复,今天刚看到这种操作
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1540 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 25ms · UTC 17:00 · PVG 01:00 · LAX 09:00 · JFK 12:00
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.