Python中如何用循环爬取未知总页数的网站?
如题,假如网址是
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)这样生成循环数来搞定。
现在有一个网站,这个自增数量太大,我应该如何用条件判断和循环来解决这个问题呢?
Python中如何用循环爬取未知总页数的网站?
scrapy rule .
import requests
from bs4 import BeautifulSoup
import time
def crawl_unknown_pages(base_url, start_page=1, max_pages=100):
"""
爬取未知总页数的网站
参数:
base_url: 基础URL,需包含{page}占位符
start_page: 起始页码
max_pages: 最大爬取页数(防止无限循环)
"""
page = start_page
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
}
while page <= max_pages:
try:
# 构建当前页URL
current_url = base_url.format(page=page)
print(f"正在爬取第 {page} 页: {current_url}")
# 发送请求
response = requests.get(current_url, headers=headers, timeout=10)
response.raise_for_status()
# 解析页面
soup = BeautifulSoup(response.text, 'html.parser')
# 检查是否到达末页(根据实际情况调整)
if is_last_page(soup):
print("已到达最后一页")
break
# 提取数据(这里需要根据实际网站结构调整)
extract_data(soup)
# 翻页
page += 1
# 礼貌延迟
time.sleep(1)
except requests.exceptions.RequestException as e:
print(f"请求第 {page} 页失败: {e}")
break
except Exception as e:
print(f"处理第 {page} 页时出错: {e}")
break
def is_last_page(soup):
"""
判断是否为最后一页
根据实际网站结构编写判断逻辑
"""
# 示例1:检查"下一页"按钮是否禁用
next_button = soup.find('a', text='下一页')
if next_button and 'disabled' in next_button.get('class', []):
return True
# 示例2:检查当前页是否为最大页码
current_page = soup.find('span', class_='current')
total_pages = soup.find('span', class_='total')
if current_page and total_pages:
return int(current_page.text) >= int(total_page.text)
# 示例3:检查内容是否为空
content = soup.find('div', class_='content')
if content and not content.find_all('div', class_='item'):
return True
return False
def extract_data(soup):
"""
提取页面数据的示例函数
"""
items = soup.find_all('div', class_='item')
for item in items:
title = item.find('h2').text.strip() if item.find('h2') else ''
print(f"标题: {title}")
# 这里添加你的数据提取逻辑
# 使用示例
if __name__ == "__main__":
# 示例URL,{page}会被替换为实际页码
url_template = "https://example.com/list?page={page}"
crawl_unknown_pages(url_template, start_page=1, max_pages=50)
核心思路就是while循环配合终止条件检测。关键点在于is_last_page()函数,你需要根据目标网站的具体结构来定制判断逻辑。常见的终止条件包括:下一页按钮不可用、到达最大页码、页面内容为空、返回404错误等。
记得设置max_pages防止意外无限循环,加上适当的延迟和异常处理。
总结:用while循环加终止条件判断来爬未知页数。
设置一个尽可能大的值,循环过程中进行判断,没爬到希望的内容就跳出循环并通知你,你再人肉看看后面还有没有,再进行下一步操作?
这种算最简单的爬取规则了吧你一直爬到没有不就好了。
while true。
然后 404 的时候 break 掉
while 不行啊
#4 请问可以稍微写一个例子吗?
#2 设置最大值会遇到一种情况,就是以后当网站的页数超过你的最大值时,你的爬虫就要去更新了
#5 谢谢,我去看下
本来就是啊,所以说尽可能大啊,比方说一百万,不够就设个三十亿(我怎么会说是我忘了 while true 呢)。
另外,爬虫肯定要不定期更新的啊,因为人家也会更新反爬规则啊。
while ture,然后判断获取 http 的状态码,是 200 就继续循环,不是就跳出循环
#10 我其实已经说了,这个设置最大值这块其实我已经会了。
感觉看别人的代码,似乎有三种写法,if/else,try/expect,while ture 这个例子我还真没见过。
#11 能大概写个小例子吗?
当然可以啊,比如查询从你发布的帖子到现在发布了多少新帖
代码如下:
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) + “贴”)
我靠,这破站真是“小气鬼”,我就查了几次,把我 IP ban 了
我用人工都能很快地尝试出来。
二分法。
#14 原来是这个原理,好的,谢谢哈,我按照这个逻辑,尝试一下
比如你找到当前页的 “下一页” 特征(标签啊各种,一般靠 re 扒出来)定义为 next_url 啥的,然后在 while True 循环里 if next_url 就行了,只要一直有就会一直爬,没有就终止了。(以上来自以前爬多页漫画网站的经验)
楼主,这个可以用二分搜索解决啊。
我给楼主写一个实现,借助 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® else 1
end = bisect.bisect_left(PageStatus(), 1, lo=1, hi=1024)
print(“the page range is [1, {0})”.format(end))
#20 二分查找是什么意思?
话说 这个代码上来说二分是最简便的算法啊
#21 你这个好像有点复杂啊,我得消化下。想问下,你这种写法和前面朋友给的 while True 方式相比,有哪些优点吗?
因为 while true 我现在可以理解,而且也可以写出来了。
#25 哦,有点理解了。
比如我有一个要爬取的网站,他的最大页面值等于 1W,但是我并不知道。
如果用 while true 的话,我相当于要依次载入 9999 次,最终到达一万次,然后 10001 次的时候,页面返回的不是 200,然后这个循环就中止了。
用二分的话,速度就要快一点?
可问题是我的目的是爬取,求最大值只是为了让我的爬虫知道要爬多少个页面,这样的话,二分法用在这里,似乎并没有大的作用?除了可以让我的爬虫可以更快的获取网页的数量。
不过感觉可以用来作为前置使用。比如我想爬取一下这个网站,然后我通过二分法,快速知道这个网站大概有多少个页面,这样的话,心里会更有数?
写个死循环,条件判断跳出不就好了。。。
抱歉,我以为你的需求是确定最大页码,如果你是要爬去所有页面的话,用 while 就行了,二分搜索多此一举。
如果你要爬取所有页面,就写个死循环,一直到 404 或者没东西返回的时候再 break。
如果你只需要确定页数,就可以先倍增再二分,比如 1、2、4、8、16 …… 假如到 1024 的时候没东西,那么页数就在 512-1024 之间,然后在这个区间二分。
不过你要注意人家可能会反爬,甚至会投毒,这个就得你自己处理了。说不定人家发现你是爬虫,会返回假数据让你永远也爬不完,所以你要监控你的数据。
反正做爬虫就要准备打持久战,小网站还好,大网站的反爬很恶心,你得做好每天都改的准备。
#29 谢谢了,又涨知识了。
#27 让我很尴尬的是,网上几乎所有的教程都没有涉及到这方面。看来是这个问题太简单了
类似递归二分法定位问题,先随便给个比较大的数值,确认无效后开始递归二分法查找边界值。
我给出代码的判断条件是是否 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] + “楼”)
http 的状态码 200
#34 我去,还有这种套路啊?我都没遇到过,一般都是 200 或者非 200 的状态码。
看来大家爬取的都是很高端的网站啊。我爬取的一般都是一些表格网站居多。
谢谢你提供的范例,我琢磨和研究下。
感觉上论坛问了之后,发现了以前很多根本没有接触到的东西。又触及到了我的知识盲区了。
是我没有理解楼主已经会设置最大页码的意思吗
😴获取这个目录下的最大页码 源代码肯定有接口或者写死在标签里的
这样直接循环遍历不挺好的么
二分实在多此一举了
“但是我不知道这个网站的准确页数”
如果真的是这样的话对用户太不友好了 设计再差的网站项目也不会忽略这点的 因此绝对有接口存在
但是那种不打算对用户开放的站点可能不会考虑…但是如果是这种站点 他的入口又是哪里呢 从哪里搞到这个 url 的,从最初的地方去找逻辑
总之这个不知道准确页数让我很迷惑
应该就是知道的
另外说一下
判断不一定要用状态码啊 如果用 xpath 为空就让他为空好了 最后不会返回顺便判断一下
二分我也有点迷惑 一样要遍历判断是否合法 二分完全是多余的
最大页数未知的并不影响你爬啊,正常地一页一页翻下去不就好了吗?下一页给的哪个就跟着翻下去,拟人的操作不就是这样吗?
还是说你想要知道所有的页码,直接并发请求列表页?没必要啊,你都说了是列表页了,主要影响速度的应该是在详情页上,跟列表页没啥关系。
#40 谢谢你的答复,今天刚看到这种操作


