如何抓取网页数据(:For循环和While循环不确定的网页(二))

优采云 发布时间: 2021-09-10 17:14

  如何抓取网页数据(:For循环和While循环不确定的网页(二))

  ·

  学习的地方

  有两种方法。

  第一种方法使用带有 break 语句的 For 循环。最后一页的页数设置为更大的参数,足以循环遍历所有页。爬行完成后,break跳出循环,结束爬行。

  第二种方法使用While循环,可以结合break语句,也可以将初始循环判断条件设置为True,从头爬到最后一页,然后改变判断条件为 False 跳出循环并结束爬行选择。

  Requests 和 Scrapy 分别使用 For 循环和 While 循环来抓取页数不确定的网页。

  总结:Requests 和 Scrapy 分别使用 For 循环和 While 循环来抓取页数不确定的网页。

  我们通常遇到的网站页码显示形式如下:

  首先是把所有的页面都可视化展示,比如之前爬过的宽和东方财富。

  文章见:

  ∞ Scrapy爬取并分析了关的6000个app,发现良心软

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

  

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

  ∞ pyspider 爬取并分析 50,000 个老虎嗅探网文章

  

  我今天要讲的就是第三种。不知有多少页,如豌豆荚:

  

  对于前两种形式的网页,爬取的方法很简单,就用For循环从第一页爬到最后一页,第三种形式不适用,因为最后一页的编号是不知道,所以循环到无法判断哪个页面结束。

  如何解决?有两种方式。

  第一种方法使用带有 break 语句的 For 循环。最后一页的页数设置为更大的参数,足以循环遍历所有页。爬行完成后,break跳出循环,结束爬行。

  第二种方法使用While循环,可以结合break语句,也可以将初始循环判断条件设置为True,从头爬到最后一页,然后改变判断条件为 False 跳出循环并结束爬行选择。

  实际案例

  下面我们以豌豆荚网站中“视频”类别下的App信息为例,通过上述两种方法抓取该类别下的所有App信息,包括App名称、评论、安装数, 和音量。

  首先对网站做一个简单的分析,可以看到页面是通过ajax加载的,GET请求自带一些参数,可以使用params参数构造URL请求,但是我不'不知道总共有多少个页面,为了保证所有页面都下载完毕,设置更大的页面数,比如100个页面甚至1000个页面。

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

  

  请求▌For循环

  主要代码如下:

  class Get_page():<br /> def __init__(self):<br /> # ajax 请求url<br /> self.ajax_url = 'https://www.wandoujia.com/wdjweb/api/category/more'<br /><br /> def get_page(self,page,cate_code,child_cate_code):<br /> params = {<br /> 'catId': cate_code,<br /> 'subCatId': child_cate_code,<br /> 'page': page,<br /> }<br /> response = requests.get(self.ajax_url, headers=headers, params=params)<br /> content = response.json()['data']['content'] #提取json中的html页面数据<br /> return content<br /><br /> def parse_page(self, content):<br /> # 解析网页内容<br /> contents = pq(content)('.card').items()<br /> data = []<br /> for content in contents:<br /> data1 = {<br /> 'app_name': content('.name').text(),<br /> 'install': content('.install-count').text(),<br /> 'volume': content('.meta span:last-child').text(),<br /> 'comment': content('.comment').text(),<br /> }<br /> data.append(data1)<br /> if data:<br /> # 写入MongoDB<br /> self.write_to_mongodb(data)<br /><br />if __name__ == '__main__':<br /> # 实例化数据提取类<br /> wandou_page = Get_page()<br /> cate_code = 5029 # 影音播放大类别编号<br /> child_cate_code = 716 # 视频小类别编号<br /> for page in range(2, 100):<br /> print('*' * 50)<br /> print('正在爬取:第 %s 页' % page)<br /> content = wandou_page.get_page(page,cate_code,child_cate_code)<br /> # 添加循环判断,如果content 为空表示此页已经下载完成了,break 跳出循环<br /> if not content == '':<br /> wandou_page.parse_page(content)<br /> sleep = np.random.randint(3,6)<br /> time.sleep(sleep)<br /> else:<br /> print('该类别已下载完最后一页')<br /> break

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

  在main函数中,if函数用于条件判断。如果内容不为空,则说明页面有内容,则循环往下。如果为空,则表示该页面已被爬取,执行else分支。 break 语句结束循环并完成抓取。

  

  抓取结果如下。可以看到该分类共爬取了41页信息。

  

  ▌While 循环

  虽然loop和For循环的思路大致相同,但是有两种写法,一种还是结合break语句,另一种是改变判断条件。

  整体代码不变,只是修改了For循环部分:

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

  或者:

  page = 2 # 设置爬取起始页数<br />page_last = False # while 循环初始条件<br />while not page_last:<br /> #...<br /> else:<br /> # break<br /> 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):<br /> pages=[]<br /> for i in range(1,10):<br /> url='http://www.example.com/?page=%s'%i<br /> page = scrapy.Request(url,callback==self.pare)<br /> pages.append(page)<br /> return pages

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

  def start_requests(self):<br /> cate_code = 5019 # 新闻阅读<br /> child_cate_code = 940 # 电子书<br /> print('*' * 50)<br /> pages = []<br /> for page in range(2,50):<br /> print('正在爬取:第 %s 页 ' %page)<br /> params = {<br /> 'catId': cate_code,<br /> 'subCatId': child_cate_code,<br /> 'page': page,<br /> }<br /> category_url = self.ajax_url + urlencode(params)<br /> pa = yield scrapy.Request(category_url,callback=self.parse)<br /> pages.append(pa)<br /> return pages<br /><br />def parse(self, response):<br /> if len(response.body) >= 100: # 判断该页是否爬完,数值定为100是因为response无内容时的长度是87<br /> jsonresponse = json.loads(response.body_as_unicode())<br /> contents = jsonresponse['data']['content']<br /> # response 是json,json内容是html,html 为文本不能直接使用.css 提取,要先转换<br /> contents = scrapy.Selector(text=contents, type="html")<br /> contents = contents.css('.card')<br /> for content in contents:<br /> item = WandoujiaItem()<br /> item['app_name'] = content.css('.name::text').extract_first()<br /> item['install'] = content.css('.install-count::text').extract_first()<br /> item['volume'] = content.css('.meta span:last-child::text').extract_first()<br /> item['comment'] = content.css('.comment::text').extract_first().strip()<br /> yield item

  以上代码简单易懂,简单说明几点:

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

  因为请求已经爬完页面,所以返回的响应结果不是空的,而是一段json内容的长度(长度是87),这里的内容键值内容为空,所以这里是判断条件选择一个大于87的值,比如100,大于100表示​​该页面有内容,小于100表示​​已被抓取。

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

  第二、当需要从文本中解析内容时,无法直接解析,需要先进行转换。

  通常我们在解析内容的时候,直接解析返回的响应,比如使用response.css()方法,但是这里,我们的分析对象不是响应,而是响应HTML文本返回的json内容,文本不能直接使用.css()方法解析,所以在解析html之前,需要在解析前添加下面这行代码进行转换。

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

  结果如下。您可以看到所有 48 个请求都已发送。事实上,这个类别只有22页内容,也就是发送了26个不必要的请求。

  

  ▌While 循环

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

  def start_requests(self):<br /> page = 2 # 设置爬取起始页数<br /> dict = {'page':page,'cate_code':cate_code,'child_cate_code':child_cate_code} # meta传递参数<br /> yield scrapy.Request(category_url,callback=self.parse,meta=dict)<br /><br />def parse(self, response):<br /> if len(response.body) >= 100: # 判断该页是否爬完,数值定为100是因为无内容时长度是87<br /> page = response.meta['page']<br /> cate_code = response.meta['cate_code']<br /> child_cate_code = response.meta['child_cate_code']<br /> #...<br /> for content in contents:<br /> yield item<br /><br /> # while循环构造url递归爬下一页<br /> page += 1<br /> params = {<br /> 'catId': cate_code,<br /> 'subCatId': child_cate_code,<br /> 'page': page,<br /> }<br /> ajax_url = self.ajax_url + urlencode(params)<br /> dict = {'page':page,'cate_code':cate_code,'child_cate_code':child_cate_code}<br /> yield scrapy.Request(ajax_url,callback=self.parse,meta=dict)

  这里简单说明几点:

  一、While循环的思路是从头开始抓取,使用parse()方法解析,然后递增页数构造下一页的URL请求,然后循环到解析直到最后一页被抓取。但是,这不会像 For 循环那样发送无用的请求。

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

  运行结果如下,可以看到请求数正好是22,完成了所有页面的App信息抓取。

  

  以上就是本文的全部内容,总结一下:

0 个评论

要回复文章请先登录注册


官方客服QQ群

微信人工客服

QQ人工客服


线