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

发布时间:2025-09-02 00:28:30 作者:益华网络 来源:undefined 浏览量(0) 点赞(0)
摘要:摘要:Requests 和 Scrapy 中分别用 For 循环和 While 循环爬取不确定页数的网页。 我们通常遇到的网站页数展现形式有这么几种: 第一种是直观地显示所有页数,比如此前爬过的酷安、东方财富网,文章见: ∞ 分析了酷安 6

摘要: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

1516    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            # 写入MongoDB30

            self.write_to_mongodb(data)

3132if __name__ == __main__

:

33    # 实例化数据提取类34

    wandou_page = Get_page()

35    cate_code = 5029 # 影音播放大类别编号36    child_cate_code = 716 # 视频小类别编号37     for page in range(2100

):

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        # break7        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

1718def parse(self, response):19    if len(response.body) >= 100:  # 判断该页是否爬完,数值定为100是因为response无内容时的长度是8720

        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

1415        # while循环构造url递归爬下一页16        page += 117

        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 循环的方法稍有不同,因此本文以豌豆荚网站为例,详细介绍了循环构造方法。

之所以写本文内容和之前的几篇文章(设置随机 UA、代理 IP),是为了下一篇文章「分析豌豆荚全网 70000+ App 信息」做铺垫,敬请期待。

二维码

扫一扫,关注我们

声明:本文由【益华网络】编辑上传发布,转载此文章须经作者同意,并请附上出处【益华网络】及本页链接。如内容、图片有任何版权问题,请联系我们进行处理。

感兴趣吗?

欢迎联系我们,我们愿意为您解答任何有关网站疑难问题!

您身边的【网站建设专家】

搜索千万次不如咨询1次

主营项目:网站建设,手机网站,响应式网站,SEO优化,小程序开发,公众号系统,软件开发等

立即咨询 15368564009
在线客服
嘿,我来帮您!