Python 爬虫工具 Requests-HTML

王 茂南 2019年4月23日08:38:5621 7898字阅读26分19秒
摘要这一篇文章主要介绍python爬虫的一些内容. 主要是关于requests-html使用的一些记录。其中会包含一些css selector和xpath的内容.

Request 的简单使用

Request构造请求参数

这里介绍一个例子,进行 url 的构造后进行请求(常用语请求一些 api 的接口,如历史天气)。这里会使用 Request 进行完成。

  1. #coding:utf-8
  2. import json,urllib
  3. try:
  4.     from urllib.parse import urlencode
  5. except ImportError:
  6.     from urllib import urlencode
  7. #from urllib.request import urlopen
  8. import requests
  9. def get_message(find_date):
  10.   url = 'http://api.k780.com'
  11.   params = {
  12.     'app' : 'weather.history',
  13.     'weaid' : '1',#城市id
  14.     'date' : find_date,#查询日期
  15.     'appkey' : '33690',
  16.     'sign' : 'bdcf684483992488f5d296a53b846283',
  17.     'format' : 'json',
  18.   }
  19.   params = urlencode(params)
  20.   try:
  21.       f = requests.get('%s?%s' % (url, params))
  22.       while f.status_code != 200: # 重复请求
  23.           f = requests.get('%s?%s' % (url, params))
  24.       html=f.content # 获取请求内容
  25.       nowapi_call=str(html)
  26.       a_result = json.loads(nowapi_call)
  27.   return a_result

上面的代码作为一个小的样例,可以在params中定制自己需要的参数。

 

使用 Requests 下载文件

我们可以使用 request 下载图片或是一些文本,直接使用 get 即可。下面是一个例子,使用request 下载并保存图片。

  1. # 下载图片内容
  2. _iamge_data = requests.get(image_url) # 图片内容
  3. while _iamge_data.status_code!=200:
  4.     _iamge_data = requests.get(image_url) # 图片内容
  5.     time.sleep(5)
  6. # 保存图片
  7. with open(_tmp.jpg', "wb") as text_file:
  8.     text_file.write(_iamge_data.content)

 

使用 Request 下载大文件

我们可以使用 request 下载一些比较大的视频。参考自下面的链接,How to download large files with requests in Python

我们使用 requests.get(url, stream) 来从一个链接下载文件,同时设置 stream 是 True 来在 Response object 中可以使用 iter_content 同时保持 connection 是 open 的。接着使用 Response.iter_content(chunk_size) 来 load 一个固定大小的 chunk_size。下面看一个简单的例子。

  1. url = 'xxxx'
  2. # 设置 stream=True
  3. response = requests.get(url, stream = True)
  4. with open('{}.txt'.format(file_name), "wb") as text_file:
  5.     for chunk in response.iter_content(chunk_size=1024):
  6.         text_file.write(chunk)

 

Request 登陆 Basic Access Authentication

有的时候我们爬取网页的时候需要一个简单的登陆。例如下所示,在登陆网站的时候,会弹出一个需要登陆的框。这种形式的验证是 HTTP Basic 的身份验证,关于 HTTP Basic 的身份验证的详细说明,可以查看链接,一文读懂HTTP Basic身份认证

Python 爬虫工具 Requests-HTML

要登陆这种类型的网站,只需要在 request 中设置 auth 即可。主要这里我们使用的是 requests.get 而不是 post.

  1. import requests
  2. from requests.auth import HTTPBasicAuth
  3. url='xxxx'
  4. r = requests.get(url=url, auth=HTTPBasicAuth('admin', 'admin'))
  5. print(r.status_code)

如果用户名和密码正确的话,请求时会自动认证成功,返回 200 状态码;如果认证失败,则返回 401 状态码。

当然,如果参数都传一个 HTTPBasicAuth 类,就显得有点烦琐了,所以 requests 提供了一个更简单的写法,可以直接传一个元组,它会默认使用 HTTPBasicAuth 这个类来认证。所以上面的代码可以直接简写如下:

  1. import requests
  2. url='xxxxxx'
  3. r = requests.get(url, auth=('admin', 'admin'))
  4. print(r.status_code)

参考资料,爬虫中的身份认证问题

 

Requests-HTML的使用

加上header和使用代理

有的时候我们需要模拟浏览器的结果, 所以我们需要加上header. 有的时候我又需要使用代理. 这两个功能在request-HTML都是很方便的进行实现的.

  1. session = HTMLSession()
  2. proxie = {
  3.     'http': 'http://127.0.0.1:1080',
  4. }
  5. headers = {
  6.     'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36',
  7.     # 'Cookie': '__cfduid=d38a3b9c5846e5db96ef23a5eb78642e91530061695'
  8. }
  9. # 进行爬取
  10. r = session.get(url,proxies=proxie,headers=headers)

数据提取CSS Selector

我们使用百度首页做一个例子,我们想要获得value中的值, 我们分别可以使用下面两种方式。

Python 爬虫工具 Requests-HTML
  1. from requests_html import HTMLSession
  2. session = HTMLSession()
  3. url = 'https://www.baidu.com/'
  4. r = session.get(url)

根据 ID 进行提取

  1. # 通过ID的方式搜索
  2. for i in r.html.find('span input#su'):
  3.     print(i.lxml.xpath('//@value'))
Python 爬虫工具 Requests-HTML

我们可以看到上面id为su的input,被span包裹,所以提取的时候写法为:span input#su.

根据 class 进行提取

  1. # 通过class的方式搜索
  2. for i in r.html.find('span input.s_btn'):
  3.     print(i.lxml.xpath('//@value'))
Python 爬虫工具 Requests-HTML

通过class进行提取的时候,使用.的记号进行提取,如这里的input的class是s_btn,就可以写成input.s_btn来进行提取。

数据提取 Xpath

XPath 是一门路径提取语言,常用于从 html/xml 文件中提取信息。它的基规则如下:

Python 爬虫工具 Requests-HTML

我们在上面的提取中,为了提取 input 中 value 属性的值,就已经使用了 xpath 了,具体可以看重新看一下上面的例子。

下面讲两个上面没有讲到的,可以利用 text() 来从 element 中提取文本,如下所示:

  1. dom_tree.xpath("//div[@class='b-content corner']//text()"):

或是使用 tostring 的方式来将 element 显示出来:

  1. for i in dom_tree.xpath("//div[@class='b-content corner']"):
  2.     print(etree.tostring(i, pretty_print=True, encoding="utf8").decode('utf-8'))

 

自动解析js代码

在request-html中提供了解析js代码的操作. 我们可以进行如下的操作来进行提取:

  1. r = session.get(url,headers=headers)
  2. r.html.render()

我们也可以直接解析js代码, 直接提取了相应的js代码进行解析

  1. # 提取js代码
  2. result = re.search('<script>(.*)</script>', js)
  3. js = result.group(1)
  4. # 运行js代码
  5. val = r.html.render(script=js, reload=False)

如果出现一些比较复杂的js的情况, 比如返回状态码是521, 爬取巴比特快讯遇到状态码“521”, 我们可以尝试使用selenium来进行操作, 相关的内容介绍参考: python selenium介绍

这里简单说明一下遇到521的情况的原因:

这时浏览器访问网站时:

  • 第一次请求:返回521状态码和一段js代码。js会生成一段cookie并重新请求访问;
  • 第二次请求:带着第一次得到的cookie去请求然后正确返回状态码200;

 

Cannot use HTMLSession within an existing event loop. Use AsyncHTMLSession instead

如果出现报错信息为"Cannot use HTMLSession within an existing event loop. Use AsyncHTMLSession instead.", 这是由于jupyter notebook造成的, 只需要不使用jupyter notebook即可.

 

一个Requests-HTML的例子

上面只是简单讲了一下Request-HTML的用法, 下面看一个具体的例子. 也是看一下爬虫通常会有哪些步骤, 一个好的代码(比较规范)会写成什么样子的.

下面的一个例子是获取网站上每一页图片的例子, 主要的步骤如下所示:

  • 读取想要爬取的网页的url
  • 获得所有的页面(如果可以, 一下获得所有的连接, 不要每次判断是否翻页)
  • 获取每一页图片的链接
  • 图片的下载

创建session, 进行初始化

导入需要的库.

  1. from requests_html import HTMLSession
  2. from multiprocessing import Pool
  3. import osthreadingtime
  4. import random

接着我们创建session, 并定义header和proxy.

  1. session = HTMLSession()
  2. proxie = {
  3.     'http': 'http://127.0.0.1:1080',
  4. }
  5. headers = {
  6.     'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36',
  7.     # 'Cookie': '__cfduid=d38a3b9c5846e5db96ef23a5eb78642e91530061695'
  8. }

 获取需要爬取的链接

有的时候, 我们会将我们需要爬取的链接写入文本, 这样可以方便我们进行修改. 我们可以使用下面的方式进行读取.

  1. # --------------
  2. # 读取文件中的url
  3. # --------------
  4. def duqu():
  5.     with open('list.txt', 'r') as f:
  6.         url_list = f.readlines()
  7.     return url_list

获取所有的链接

我们一次把获取要爬取的所有页面的链接(如果可以的话), 后面就不用进行翻页的判断了.

  1. # --------------------
  2. # 获得漫画所有页面的链接
  3. # --------------------
  4. def get_page_list(url):
  5.     print('开始获取漫画分页列表')
  6.     firstPage = session.get(url,proxies=proxie)
  7.     container = firstPage.html.find('.entry-content .page-links', first=True).links
  8.     title = firstPage.html.find('header h1.entry-title', first=True).text
  9.     num = len(container) # 漫画链接的数量
  10.     link_list = [url+'/'+str(x) for x in range(1,num+2)] # 生成所有需要的链接
  11.     print("漫画链接: \n{}".format(link_list))
  12.     print("漫画标题: \n{}".format(title))
  13.     print('获取漫画分页列表完成')
  14.     return title,link_list

 获得图片链接

上面我们获取了每一页的链接, 下面我们获取每一页图片的链接, 我们暂时只把图片的链接保存下来, 先不对图片进行保存, 保存在后面进行操作.

  1. # ----------------
  2. # 获得图片所有的链接
  3. # ----------------
  4. def get_img_list(page_list):
  5.     """
  6.     按顺序获得每一页的所有图片链接
  7.     """
  8.     img_list = []
  9.     for page in page_list:
  10.         r = session.get(page,proxies=proxie)
  11.         time.sleep(random.random()*5) # 每次获得图片的时候停顿一下, 不要跑太快了
  12.         single_page_list = r.html.find('.entry-content div.single-content p')
  13.         img_list += [img.find('p img')[1].attrs['src'] for img in single_page_list]
  14.     # print(img_list)
  15.     print('获取图片列表完成')
  16.     return img_list

图片的保存

有了图片的链接之后就是进行图片的保存了, 我们加入了一个文件夹是否存在的判断. 这里图片的保存我们使用多线程来进行操作.

  1. # ---------
  2. # 图片的保存
  3. # ---------
  4. def mkdir(path):
  5.     # 引入模块
  6.     import os
  7.     # 去除首位空格
  8.     path=path.strip()
  9.     # 去除尾部 \ 符号
  10.     path=path.rstrip("\\")
  11.     # 判断路径是否存在
  12.     # 存在     True
  13.     # 不存在   False
  14.     isExists=os.path.exists(path)
  15.     # 判断结果
  16.     if not isExists:
  17.         # 如果不存在则创建目录
  18.         # 创建目录操作函数
  19.         os.makedirs(path)
  20.         print(path+' 创建成功')
  21.         return True
  22.     else:
  23.         # 如果目录存在则不创建,并提示目录已存在
  24.         print(path+' 目录已存在')
  25.         return False
  26. def saver(mkpath, img_url, n ,total):
  27.     """
  28.     mkpath: 图片保存的文件夹
  29.     img_url: 要保存的图片的url
  30.     n: 要保持的图片的编号
  31.     total: 所有的图片的数量
  32.     """
  33.     img_url = img_url.strip()
  34.     res = session.get(img_url,proxies=proxie,headers=headers)
  35.     # print(res.content)
  36.     with open(mkpath + '\\%03d.jpg' % n,'wb') as f:
  37.         f.write(res.content)
  38.     print('第%03d张图片下载完成/共%03d张图片' % (n,total))
  39. def save(img_urls,title):
  40.     n = 1
  41.     mkpath = './comic/' + title # 漫画保存的路径
  42.     total = len(img_urls)
  43.     mkdir(mkpath) # 如果没有对应文件夹就进行创建
  44.     for img_url in img_urls:
  45.         t = threading.Thread(target=saver,args=(mkpath,img_url,n,total))
  46.         t.start()
  47.         t.join()
  48.         n += 1

主程序

把上面所有需要的东西都定义好之后, 我们把他们按顺序组合起来. 这样写方便后面使用多进程.

  1. # -------
  2. # 主程序
  3. # ------
  4. def run(url):
  5.     title,page_list = get_page_list(url)
  6.     imgurls = get_img_list(page_list)
  7.     save(imgurls,title)

开始爬取

最后一步就是开始爬取了, 我们在这里使用了多进程的方式, 可以同时爬取多个链接.

  1. if __name__ == '__main__':
  2.     url_list = duqu()
  3.     p = Pool(2)
  4.     print('begin')
  5.     for url in url_list:
  6.         url = url.strip()
  7.         p.apply_async(run,args=(url,))
  8.     p.close()
  9.     p.join()
  10.     print('All finished!!!')

 

参考链接

Python 爬虫工具 Requests-HTML

  • 微信公众号
  • 关注微信公众号
  • weinxin
  • QQ群
  • 我们的QQ群号
  • weinxin
王 茂南
  • 本文由 发表于 2019年4月23日08:38:56
  • 转载请务必保留本文链接:https://mathpretty.com/9571.html
匿名

发表评论

匿名网友 填写信息

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

评论:2   其中:访客  1   博主  1
    • geada
      geada

      “Cannot use HTMLSession within an existing event loop. Use AsyncHTMLSession instead”
      能详细讲一下怎么操作吗?我现在也遇到了这个问题,html.render()方法一运行就报错,github上也没有合适的解决方案(已经把Jupyter Notebook从Anaconda里面卸载了,还是有问题)

        • 王 茂南
          王 茂南

          @ geada 你好,你现在解决了嘛。我当时是不使用 jupyter notebook 就可以解决,直接使用脚本来运行。