php 循环抓取网页内容(PythonFor和While循环不确定页数的网页需要学习的地方)
优采云 发布时间: 2021-11-24 22:20php 循环抓取网页内容(PythonFor和While循环不确定页数的网页需要学习的地方)
本文转载自以下网站:Python For和While循环爬取页数不确定的网页
学习的地方
有两种方法。
第一种方法使用带有 break 语句的 For 循环。最后一页的页数设置为更大的参数,足以循环遍历所有页面。当爬行完成时,中断跳出循环并结束爬行。
第二种方法使用While循环,可以和break语句结合使用,也可以将初始循环判断条件设置为True,从头爬到最后一页,然后将判断条件改为False进行跳转退出循环并结束爬行。
Requests 和 Scrapy 分别使用 For 循环和 While 循环来抓取页数不确定的网页。
摘要:Requests 和 Scrapy 分别使用 For 循环和 While 循环来抓取页面数量不确定的网页。
我们通常遇到的网站页码的显示形式有以下几种:
一是将所有页码可视化显示,比如之前爬取的宽和东方财富。
文章见:
∞ Scrapy爬取并分析了关的6000个app,发现良心软了
∞ 50行代码爬取东方财富网百万行财务报表数据
二是不直观显示网页总数,只能在后台查看。比如之前爬过的虎嗅网络,见文章:
∞ pyspider 爬取并分析 50,000 个老虎嗅探网文章
第三个是我今天要讲的。不知道有多少页,比如豌豆荚:
对于前两种形式的网页,爬取方法很简单,只需要使用For循环从第一页爬到最后一页即可。第三种形式不适用,因为不知道最后一页的编号,所以无法判断循环到哪一页结束。
如何解决?有两种方法。
第一种方法使用带有 break 语句的 For 循环。最后一页的页数设置为更大的参数,足以循环遍历所有页面。当爬行完成时,中断跳出循环并结束爬行。
第二种方法使用While循环,可以和break语句结合使用,也可以将初始循环判断条件设置为True,从头爬到最后一页,然后将判断条件改为False进行跳转退出循环并结束爬行。
实际案例
下面,我们以豆豆荚网站中“视频”类别下的App信息为例,通过上述两种方式,抓取该类别下的所有App信息,包括App名称、评论、安装数、和音量。
首先简单分析一下网站,可以看到页面是通过ajax加载的,GET请求自带了一些参数,可以使用params参数构造一个URL请求,但是我没有知道总共有多少页。为了保证所有页面都被下载,设置更大的页面数,比如100页甚至1000页。
下面我们尝试使用For和While循环爬取。
请求▌For 循环
主要代码如下:
class Get_page():
def __init__(self):
# ajax 请求url
self.ajax_url = \'https://www.wandoujia.com/wdjweb/api/category/more\'
def get_page(self,page,cate_code,child_cate_code):
params = {
\'catId\': cate_code,
\'subCatId\': child_cate_code,
\'page\': page,
}
response = requests.get(self.ajax_url, headers=headers, params=params)
content = response.json()[\'data\'][\'content\'] #提取json中的html页面数据
return content
def parse_page(self, content):
# 解析网页内容
contents = pq(content)(\'.card\').items()
data = []
for content in contents:
data1 = {
\'app_name\': content(\'.name\').text(),
\'install\': content(\'.install-count\').text(),
\'volume\': content(\'.meta span:last-child\').text(),
\'comment\': content(\'.comment\').text(),
}
data.append(data1)
if data:
# 写入MongoDB
self.write_to_mongodb(data)
if __name__ == \'__main__\':
# 实例化数据提取类
wandou_page = Get_page()
cate_code = 5029 # 影音播放大类别编号
child_cate_code = 716 # 视频小类别编号
for page in range(2, 100):
print(\'*\' * 50)
print(\'正在爬取:第 %s 页\' % page)
content = wandou_page.get_page(page,cate_code,child_cate_code)
# 添加循环判断,如果content 为空表示此页已经下载完成了,break 跳出循环
if not content == \'\':
wandou_page.parse_page(content)
sleep = np.random.randint(3,6)
time.sleep(sleep)
else:
print(\'该类别已下载完最后一页\')
break
在这里,首先创建了一个 Get_page 类。get_page 方法用于获取Response 返回的json 数据。通过网站解析json后,发现要提取的内容是data字段下的content key包裹的一段html文本。使用parse_page方法中的pyquery函数进行解析,最终提取出App名称、评论、安装数、体积这四个信息来完成爬取。
在main函数中,if函数用于条件判断。如果内容不为空,则表示页面有内容,则循环往下爬,如果为空,则表示页面已经被爬取,执行else分支下的break语句。结束循环并完成爬行。
爬取结果如下,可以看到该分类下共爬取了41页信息。
▌While 循环
while循环和For循环的思路大致相同,但是有两种写法,一种还是结合break语句,一种是改变判断条件。
整体代码不变,只修改For循环部分:
page = 2 # 设置爬取起始页数
while True:
print(\'*\' * 50)
print(\'正在爬取:第 %s 页\' %page)
content = wandou_page.get_page(page,cate_code,child_cate_code)
if not content == \'\':
wandou_page.parse_page(content)
page += 1
sleep = np.random.randint(3,6)
time.sleep(sleep)
else:
print(\'该类别已下载完最后一页\')
break
或者:
page = 2 # 设置爬取起始页数
page_last = False # while 循环初始条件
while not page_last:
#...
else:
# break
page_last = True # 更改page_last 为 True 跳出循环
结果如下,可以看到For循环的结果是一样的。
我们可以测试其他类别下的网页,例如选择“K歌”类别,代码为:718,然后只需要相应地修改main函数中的child_cate_code,再次运行程序,就可以看到下一次共抓取此类别 32 个页面。
由于Scrapy中的写入方式与Requests略有不同,所以接下来我们将在Scrapy中再次实现两种循环爬取方式。
Scrapy▌For 循环
在Scrapy中使用For循环递归爬取的思路很简单,就是先批量生成所有请求的URL,包括最后一个无效的URL,然后在parse方法中加入if来判断和过滤无效请求,然后抓取所有页面。由于Scrapy依赖于Twisted框架,所以采用了异步请求处理方式,也就是说Scrapy在发送请求的同时解析内容,所以会发送很多无用的请求。
def start_requests(self):
pages=[]
for i in range(1,10):
url=\'http://www.example.com/?page=%s\'%i
page = scrapy.Request(url,callback==self.pare)
pages.append(page)
return pages
下面,我们选择豌豆荚“新闻阅读”类别下的“电子书”App页面信息,使用For循环尝试爬取。主要代码如下:
def start_requests(self):
cate_code = 5019 # 新闻阅读
child_cate_code = 940 # 电子书
print(\'*\' * 50)
pages = []
for page in range(2,50):
print(\'正在爬取:第 %s 页 \' %page)
params = {
\'catId\': cate_code,
\'subCatId\': child_cate_code,
\'page\': page,
}
category_url = self.ajax_url + urlencode(params)
pa = yield scrapy.Request(category_url,callback=self.parse)
pages.append(pa)
return pages
def parse(self, response):
if len(response.body) >= 100: # 判断该页是否爬完,数值定为100是因为response无内容时的长度是87
jsonresponse = json.loads(response.body_as_unicode())
contents = jsonresponse[\'data\'][\'content\']
# response 是json,json内容是html,html 为文本不能直接使用.css 提取,要先转换
contents = scrapy.Selector(text=contents, type="html")
contents = contents.css(\'.card\')
for content in contents:
item = WandoujiaItem()
item[\'app_name\'] = content.css(\'.name::text\').extract_first()
item[\'install\'] = content.css(\'.install-count::text\').extract_first()
item[\'volume\'] = content.css(\'.meta span:last-child::text\').extract_first()
item[\'comment\'] = content.css(\'.comment::text\').extract_first().strip()
yield item
上面的代码简单易懂,简单说明几点:
一、 判断当前页面是否被爬取的判断条件改为response.body的长度大于100。
因为请求已经爬取完成页面,返回的响应结果不是空的,而是一段json内容的长度(长度为87),其中content key value content为空,所以选择判断条件这里)一个大于87的值就足够了,比如100,即如果大于100,则表示该页面有内容,如果小于100,则表示该页面已被抓取.
{"state":{"code":2000000,"msg":"Ok","tips":""},"data":{"currPage":-1,"content":""}}
二、 当需要从文本中解析内容时,无法直接解析,需要先进行转换。
正常情况下,我们在解析内容的时候直接解析返回的响应,比如使用response.css()方法,但是这里,我们的解析对象不是响应,而是响应返回的json内容中的html文本。文本不能直接使用.css()方法解析,所以在解析html之前,需要在解析前添加如下代码行。
contents = scrapy.Selector(text=contents, type="html")
结果如下。您可以看到所有 48 个请求都已发送。事实上,这个类别的内容只有22页,也就是发送了26个不必要的请求。
▌While 循环
接下来,我们使用While循环再次尝试抓取,代码省略了与For循环中相同的部分:
def start_requests(self):
page = 2 # 设置爬取起始页数
dict = {\'page\':page,\'cate_code\':cate_code,\'child_cate_code\':child_cate_code} # meta传递参数
yield scrapy.Request(category_url,callback=self.parse,meta=dict)
def parse(self, response):
if len(response.body) >= 100: # 判断该页是否爬完,数值定为100是因为无内容时长度是87
page = response.meta[\'page\']
cate_code = response.meta[\'cate_code\']
child_cate_code = response.meta[\'child_cate_code\']
#...
for content in contents:
yield item
# while循环构造url递归爬下一页
page += 1
params = {
\'catId\': cate_code,
\'subCatId\': child_cate_code,
\'page\': page,
}
ajax_url = self.ajax_url + urlencode(params)
dict = {\'page\':page,\'cate_code\':cate_code,\'child_cate_code\':child_cate_code}
yield scrapy.Request(ajax_url,callback=self.parse,meta=dict)
在此,简单说明几点:
一、While循环的思路是从头开始爬取,使用parse()方法解析,然后递增页数构造下一页的URL请求,然后循环解析直到最后一页被抓取。, 这不会像 For 循环那样发送无用的请求。
二、parse()方法在构造下一页请求时需要用到start_requests()方法中的参数,可以使用meta方法传递参数。
运行结果如下,可以看到请求数正好是22,完成了所有页面的App信息爬取。
以上就是本文的全部内容,总结一下: