都不知道有多少页,还爬个锤子虫?

本文介绍了使用 For 循环和 While 循环爬取不确定页数网页的方法。对于前两种能直观或后台查看总页数的网页,用 For 循环从首页爬到尾页即可;对于不确定页数的网页,可使用 For 循环配合 break 语句或 While 循环。文中以豌豆荚网站为例,分别在 Requests 和 Scrapy 中实现了两种循环的爬取方式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

摘要:Requests 和 Scrapy 中分别用 For 循环和 While 循环爬取不确定页数的网页。

写在前面:本文有不少超链接,建议点击底部「阅读原文」进行跳转查看。

我们通常遇到的网站页数展现形式有这么几种:

第一种是直观地显示所有页数,比如此前爬过的酷安、东方财富网,
文章见:

∞ 分析了酷安 6000 款 App,还有这么多好东西

∞ 50 行代码爬取东方财富网百万行财务报表数据

 

 

第二种是不直观显示网页总页数,需要在后台才可以查看到,比如之前爬过的虎嗅网,文章见:

∞ pyspider 爬取并分析虎嗅网 5 万篇文章

 

 

第三种是今天要说的,不知道具体有多少页的网页,比如豌豆荚:

 

 

对于,前两种形式的网页,爬取方法非常简单,使用 For 循环从首页爬到尾页就行了,第三种形式则不适用,因为不知道尾页的页数,所以循环到哪一页结束无法判断。

那如何解决呢?有两种方法。

第一种方式 使用 For 循环配合 break 语句,尾页的页数设置一个较大的参数,足够循环爬完所有页面,爬取完成时,break 跳出循环,结束爬取。

第二种方法 使用 While 循环,可以结合 break 语句,也可以设起始循环判断条件为 True,从头开始循环爬取直到爬完最后一页,然后更改判断条件为 False 跳出循环,结束爬取。

1 实际案例

下面,我们以 豌豆荚 网站中「视频」类别下的 App 信息为例,使用上面两种方法抓取该分类下的所有 App 信息,包括 App 名称、评论、安装数量和体积。

首先,简要分析下网站,可以看到页面是通过 Ajax 加载的,GET 请求附带一些参数,可以使用 params 参数构造 URL 请求,但不知道一共有多少页,为了确保下载完所有页,设置较大的页数,比如 100页 甚至 1000 页都行。

下面我们尝试使用 For 和 While 循环爬取 。

 

 

2 Requests

▌For 循环

主要代码如下:

 1class Get_page():
 2    def __init__(self):
 3        # ajax 请求url
 4        self.ajax_url = 'https://www.wandoujia.com/wdjweb/api/category/more'
 5
 6    def get_page(self,page,cate_code,child_cate_code):
 7        params = {
 8            'catId': cate_code,
 9            'subCatId': child_cate_code,
10            'page': page,
11        }
12        response = requests.get(self.ajax_url, headers=headers, params=params)
13        content = response.json()['data']['content'] #提取json中的html页面数据
14        return content
15
16    def parse_page(self, content):
17        # 解析网页内容
18        contents = pq(content)('.card').items()
19        data = []
20        for content in contents:
21            data1 = {
22                'app_name': content('.name').text(),
23                'install': content('.install-count').text(),
24                'volume': content('.meta span:last-child').text(),
25                'comment': content('.comment').text(),
26            }
27            data.append(data1)
28        if data:
29            # 写入MongoDB
30            self.write_to_mongodb(data)
31
32if __name__ == '__main__':
33    # 实例化数据提取类
34    wandou_page = Get_page()
35    cate_code = 5029 # 影音播放大类别编号
36    child_cate_code = 716 # 视频小类别编号
37     for page in range(2, 100):
38        print('*' * 50)
39        print('正在爬取:第 %s 页' % page)
40        content = wandou_page.get_page(page,cate_code,child_cate_code)
41        # 添加循环判断,如果content 为空表示此页已经下载完成了,break 跳出循环
42        if not content == '':
43            wandou_page.parse_page(content)
44            sleep = np.random.randint(3,6)
45            time.sleep(sleep)
46        else:
47            print('该类别已下载完最后一页')
48            break

这里,首先创建了一个 Get_page 类,get_page 方法用于获取 Response 返回的 json 数据,通过 json.cn 网站解析 json 解析后发现需要提取的内容是一段包裹在 data 字段下 content 键中的 html 文本,可以使用 parse_page 方法中的 pyquery 函数进行解析,最后提取出 App 名称、评论、安装数量和体积四项信息,完成抓取。

在主函数中,使用了 if 函数进行条件判断,若 content 不为空,表示该页有内容,则循环爬下去,若为空则表示此页面已完成了爬取,执行 else 分支下的 break 语句结束循环,完成爬取。

 

 

爬取结果如下,可以看到该分类下一共完成了全部 41 页的信息抓取。

 

 

▌While 循环

While 循环和 For 循环思路大致相同,不过有两种写法,一种仍然是结合 break 语句,一种则是更改判断条件。

总体代码不变,只需修改 For 循环部分:

 1page = 2 # 设置爬取起始页数
 2while True:
 3    print('*' * 50)
 4    print('正在爬取:第 %s 页' %page)
 5    content = wandou_page.get_page(page,cate_code,child_cate_code)
 6    if not content == '':
 7        wandou_page.parse_page(content)
 8        page += 1
 9        sleep = np.random.randint(3,6)
10        time.sleep(sleep)
11    else:
12        print('该类别已下载完最后一页')
13        break

或者:

1page = 2 # 设置爬取起始页数
2page_last = False # while 循环初始条件
3while not page_last:
4   #...
5    else:
6        # break
7        page_last = True # 更改page_last 为 True 跳出循环

结果如下,可以看到和 For 循环的结果是一样的。

 

 

我们可以再测试一下其他类别下的网页,比如选择「K歌」类别,编码为:718,然后只需要对应修改主函数中的child_cate_code 即可,再次运行程序,可以看到该类别下一共爬取了 32 页。

 

 

由于 Scrapy 中的写法和 Requests 稍有不同,所以接下来,我们在 Scrapy 中再次实现两种循环的爬取方式 。

3 Scrapy

▌For 循环

Scrapy 中使用 For 循环递归爬取的思路非常简单,即先批量生成所有请求的 URL,包括最后无效的 URL,后续在 parse 方法中添加 if 判断过滤无效请求,然后爬取所有页面。由于 Scrapy 依赖于Twisted框架,采用的是异步请求处理方式,也就是说 Scrapy 边发送请求边解析内容,所以这会发送很多无用请求。

1def start_requests(self):
2    pages=[]
3    for i in range(1,10):
4        url='http://www.example.com/?page=%s'%i
5        page = scrapy.Request(url,callback==self.pare)
6        pages.append(page)
7    return pages

下面,我们选取豌豆荚「新闻阅读」分类下的「电子书」类 App 页面信息,使用 For 循环尝试爬取,主要代码如下:

 1def start_requests(self):
 2    cate_code = 5019 # 新闻阅读
 3    child_cate_code = 940 # 电子书
 4    print('*' * 50)
 5    pages = []
 6    for page in range(2,50):
 7        print('正在爬取:第 %s 页 ' %page)
 8        params = {
 9        'catId': cate_code,
10        'subCatId': child_cate_code,
11        'page': page,
12        }
13        category_url = self.ajax_url + urlencode(params)
14        pa = yield scrapy.Request(category_url,callback=self.parse)
15        pages.append(pa)
16    return pages
17
18def parse(self, response):
19    if len(response.body) >= 100:  # 判断该页是否爬完,数值定为100是因为response无内容时的长度是87
20        jsonresponse = json.loads(response.body_as_unicode())
21        contents = jsonresponse['data']['content']
22        # response 是json,json内容是html,html 为文本不能直接使用.css 提取,要先转换
23        contents = scrapy.Selector(text=contents, type="html")
24        contents = contents.css('.card')
25        for content in contents:
26            item = WandoujiaItem()
27            item['app_name'] = content.css('.name::text').extract_first()
28            item['install'] = content.css('.install-count::text').extract_first()
29            item['volume'] = content.css('.meta span:last-child::text').extract_first()
30            item['comment'] = content.css('.comment::text').extract_first().strip()
31            yield item

上面代码很好理解,简要说明几点:

第一、判断当前页是否爬取完成的判断条件改为了 response.body 的长度大于 100。

因为请求已爬取完成的页面,返回的 Response 结果是不为空的,而是有长度的 json 内容(长度为 87),其中 content 键值内容才为空,所以这里判断条件选择比 87 大的数值即可,比如 100,即大于 100 的表示此页有内容,小于 100 表示此页已爬取完成。

1{"state":{"code":2000000,"msg":"Ok","tips":""},"data":{"currPage":-1,"content":""}}

第二、当需要从文本中解析内容时,不能直接解析,需要先转换。

通常情况下,我们在解析内容时是直接对返回的 response 进行解析,比如使用 response.css() 方法,但此处,我们的解析对象不是 response,而是 response 返回的 json 内容中的 html 文本,文本是不能直接使用 .css() 方法解析的,所以在对 html 进行解析之前,需要添加下面一行代码转换后才能解析。

1 contents = scrapy.Selector(text=contents, type="html")

结果如下,可以看到发送了全部 48 个请求,实际上该分类只有 22 页内容,即多发送了无用的 26 个请求。

 

 

▌While 循环

接下来,我们使用 While 循环再次尝试抓取,代码省略了和 For 循环中相同的部分:

 1def start_requests(self):
 2        page = 2 # 设置爬取起始页数
 3        dict = {'page':page,'cate_code':cate_code,'child_cate_code':child_cate_code} # meta传递参数
 4        yield scrapy.Request(category_url,callback=self.parse,meta=dict)
 5
 6def parse(self, response):
 7    if len(response.body) >= 100:  # 判断该页是否爬完,数值定为100是因为无内容时长度是87
 8        page = response.meta['page']
 9        cate_code = response.meta['cate_code']
10        child_cate_code = response.meta['child_cate_code']
11       #...
12       for content in contents:
13            yield item
14
15        # while循环构造url递归爬下一页
16        page += 1
17        params = {
18                'catId': cate_code,
19                'subCatId': child_cate_code,
20                'page': page,
21                }
22        ajax_url = self.ajax_url + urlencode(params)
23        dict = {'page':page,'cate_code':cate_code,'child_cate_code':child_cate_code}
24        yield scrapy.Request(ajax_url,callback=self.parse,meta=dict)

这里,简要说明几点:

第一、While 循环的思路是先从头开始爬取,使用 parse() 方法进行解析,然后递增页数构造下一页的 URL 请求,再循环解析,直到爬取完最后一页即可,这样 不会像 For 循环那样发送无用的请求

第二、parse() 方法构造下一页请求时需要利用 start_requests() 方法中的参数,可以 使用 meta 方法来传递参数

运行结果如下,可以看到请求数量刚好是 22 个,也就完成了所有页面的 App 信息爬取。

 

 

以上,就是本文的所有内容,小结一下:

  • 在爬取不确定页数的网页时,可以采取 For 循环和 While 循环两种思路,方法大致相同。

  • 在 Requests 和 Scrapy 中使用 For 循环和 While 循环的方法稍有不同,因此本文以豌豆荚网站为例,详细介绍了循环构造方法。

<think>好的,用户遇到了Git合并本地更改会被覆盖的问题,需要解决方案。根据之前的引用资料,解决方法主要有commit、stash和revert三种。首先,我得确认用户的具体情况,比如他们的本地更改是否已经完成,是否需要保留,或者是否可以丢弃。用户可能太清楚这三种方法的适用场景,需要详细解释每个选项的步骤和优缺点。比如,commit适用于需要保留更改的情况,stash适合临保存以便后续恢复,而revert则是直接撤销更改,适用于需要保留的情况。需要结构清晰,分点说明,每个方法都给出操作步骤和适用场景,帮助用户理解哪种情况最适合他们。还要注意使用中文,避免技术术语过于复杂,让用户容易理解。同,生成相关问题,可能涉及如何恢复stash的内容,或者如何避免频繁冲突等,扩展用户的知识点。另外,要确保LaTeX格式正确,但在这个问题中可能用上数学公式,所以主要关注代码块和引用标识。检查引用是否正确,比如引用[^1]和[^2]的来源是否相关,用户提供的引用内容是否准确对应解决方案。 最后,确保回答真实可靠,操作步骤准确无误,避免误导用户。可能还需要提醒用户根据具体情况选择方法,比如如果有未完成的实验性代码,stash可能是更好的选择,而如果是确定要保留的修改,则应该commit。</think>要解决Git合并本地更改被覆盖的问题,可根据同场景选择以下三种方法: ### 一、提交本地更改(commit) **适用场景**:当本地更改已测试通过且需要保留到版本历史中。 **操作步骤**: 1. 将改动添加到暂存区: ```bash git add . # 添加所有文件 # 或指定文件 git add file1.txt file2.py ``` 2. 提交更改并添加描述: ```bash git commit -m "完成用户登录模块优化" ``` 3. 执行合并操作: ```bash git merge origin/main ``` **优点**:保留完整的开发记录,便于后续追溯[^1]。 ### 二、暂存更改(stash) **适用场景**:需要临保存未完成的修改(如调试代码),优先处理合并操作。 **操作步骤**: 1. 保存当前工作状态: ```bash git stash # 可选添加备注 git stash save "用户登录模块调试代码" ``` 2. 查看暂存记录列表: ```bash git stash list ``` 3. 合并远程分支: ```bash git merge origin/main ``` 4. 恢复暂存内容: ```bash git stash pop # 恢复并删除最近一次暂存 # 或指定特定暂存 git stash apply stash@{1} ``` **优点**:保持工作目录整洁,特别适合处理紧急合并任务[^2]。 ### 三、撤销本地更改(revert) **适用场景**:本地修改可丢弃(如实验性代码或错误修改)。 **操作步骤**: 1. 撤销所有未提交的修改: ```bash git checkout -- . # 全部文件 # 或指定文件 git checkout -- file1.txt ``` 2. 执行合并操作: ```bash git merge origin/main ``` **风险提示**:此操作可逆,需确认文件无需保留。 --- ### 对比决策表 | 方法 | 保留记录 | 可恢复性 | 适用阶段 | |---------|----------|----------|------------------| | commit | ✅ | ✅ | 功能开发完成 | | stash | ❌ | ✅ | 开发中途临切换 | | revert | ❌ | ❌ | 放弃无效修改 | ### 扩展应用场景 - 当存在多个暂存,可用`git stash list`查看记录,通过`git stash apply stash@{n}`精准恢复 - 合并后出现冲突,可使用`git mergetool`可视化工具解决冲突 - 长期分支开发推荐使用`git rebase`保持提交历史线性
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

1024小神

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值