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

2019-07-27 13:20:26 +08:00
 solider245

如题,假如网址是

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)这样生成循环数来搞定。

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

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

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

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

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

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

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

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

不过感觉可以用来作为前置使用。比如我想爬取一下这个网站,然后我通过二分法,快速知道这个网站大概有多少个页面,这样的话,心里会更有数?
xuanbg
2019-07-27 17:46:46 +08:00
写个死循环,条件判断跳出不就好了。。。
dongyx
2019-07-27 17:47:31 +08:00
@solider245 抱歉,我以为你的需求是确定最大页码,如果你是要爬去所有页面的话,用 while 就行了,二分搜索多此一举。
daozhihun
2019-07-27 17:49:23 +08:00
如果你要爬取所有页面,就写个死循环,一直到 404 或者没东西返回的时候再 break。
如果你只需要确定页数,就可以先倍增再二分,比如 1、2、4、8、16 …… 假如到 1024 的时候没东西,那么页数就在 512-1024 之间,然后在这个区间二分。
不过你要注意人家可能会反爬,甚至会投毒,这个就得你自己处理了。说不定人家发现你是爬虫,会返回假数据让你永远也爬不完,所以你要监控你的数据。
反正做爬虫就要准备打持久战,小网站还好,大网站的反爬很恶心,你得做好每天都改的准备。
dongyx
2019-07-27 17:51:17 +08:00
@daozhihun 倍增确定上界好赞,送一个感谢给你
solider245
2019-07-27 18:02:36 +08:00
@daozhihun #29 谢谢了,又涨知识了。
solider245
2019-07-27 18:03:06 +08:00
@xuanbg #27 让我很尴尬的是,网上几乎所有的教程都没有涉及到这方面。看来是这个问题太简单了
zgl263885
2019-07-27 21:24:25 +08:00
类似递归二分法定位问题,先随便给个比较大的数值,确认无效后开始递归二分法查找边界值。
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
2019-07-27 22:59:57 +08:00
http 的状态码 200
solider245
2019-07-28 06:12:24 +08:00
@cherbim #34 我去,还有这种套路啊?我都没遇到过,一般都是 200 或者非 200 的状态码。
看来大家爬取的都是很高端的网站啊。我爬取的一般都是一些表格网站居多。
谢谢你提供的范例,我琢磨和研究下。
感觉上论坛问了之后,发现了以前很多根本没有接触到的东西。又触及到了我的知识盲区了。
kppwp
2019-07-28 08:56:18 +08:00
是我没有理解楼主已经会设置最大页码的意思吗
😴获取这个目录下的最大页码 源代码肯定有接口或者写死在标签里的
这样直接循环遍历不挺好的么
二分实在多此一举了
kppwp
2019-07-28 09:06:54 +08:00
“但是我不知道这个网站的准确页数”
如果真的是这样的话对用户太不友好了 设计再差的网站项目也不会忽略这点的 因此绝对有接口存在
但是那种不打算对用户开放的站点可能不会考虑......但是如果是这种站点 他的入口又是哪里呢 从哪里搞到这个 url 的,从最初的地方去找逻辑

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

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

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

这种情况我一般是在浏览器手动二分试试最大的页数。楼上说的先倍增再二分,这个 tips 学到了 :) 比我直接瞎猜上界靠谱
locoz
2019-07-28 14:29:15 +08:00
最大页数未知的并不影响你爬啊,正常地一页一页翻下去不就好了吗?下一页给的哪个就跟着翻下去,拟人的操作不就是这样吗?
还是说你想要知道所有的页码,直接并发请求列表页?没必要啊,你都说了是列表页了,主要影响速度的应该是在详情页上,跟列表页没啥关系。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/586668

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX