资讯专栏INFORMATION COLUMN

requests-html库初识 + 无资料解BUG之 I/O error : encoder er

mozillazg / 2404人阅读

摘要:目标站点分析本次要采集的目标网站为,目标站点描述为全球名站。由于上述代码太少了,完全不够今日代码量,我们顺手将其修改为多线程形式。

本篇博客是《爬虫 120 例》的第 30 例,新学习一个爬虫框架 requests-html,该框架作者就是 requests 的作者,所以盲猜就很好用啦。

知识铺垫工作

requests-html 模块安装使用 pip install requests-html 即可,官方手册查询地址:https://requests-html.kennethreitz.org/,官方并没有直接的中文翻译,在检索过程中,确实发现了一版中文手册,在文末提供。

先看一下官方对该库的基本描述:

  • Full JavaScript support!(完全支持 JS,这里手册还重点标记了一下,初学阶段可以先忽略)
  • CSS Selectors (a.k.a jQuery-style, thanks to PyQuery).(集成了 pyquery 库,支持 css 选择器)
  • XPath Selectors, for the faint at heart.(支持 XPath 选择器)
  • Mocked user-agent (like a real web browser).(mock UA 数据,这点不错)
  • Automatic following of redirects.(自动跟踪重定向)
  • Connection–pooling and cookie persistence.(持久性 COOKIE)
  • The Requests experience you know and love, with magical parsing abilities.(额,这最后一点,各位自己领悟吧)

Only Python 3.6 is supported. 仅支持 Python 3.6 ,实测发现 3.6 以上版本依旧可以。

对于该库的简单使用,代码如下所示:

from requests_html import HTMLSessionsession = HTMLSession()r = session.get("https://python.org/")print(r)

首先从 requests_html 库导入 HTMLSession 类,然后将其实例化之后,调用其 get 方法,发送请求,得到的 r 输出为 ,后续即可使用内置的解析库对数据进行解析。

由于该库是解析 html 对象,所以可以查看对应的 html 对象包含哪些方法与与属性。

通过 dir 函数查阅。

print(dir(r.html))# 输出如下内容:["__aiter__", "__anext__", "__class__", "__delattr__", "__dict__", "__dir__", "__doc__", "__eq__", "__format__", "__ge__","__getattribute__", "__gt__", "__hash__", "__init__", "__init_subclass__", "__iter__", "__le__", "__lt__", "__module__", "__ne__","__new__", "__next__", "__reduce__", "__reduce_ex__", "__repr__", "__setattr__", "__sizeof__", "__str__", "__subclasshook__","__weakref__", "_async_render", "_encoding", "_html", "_lxml", "_make_absolute", "_pq", "absolute_links", "add_next_symbol","arender", "base_url", "default_encoding", "element", "encoding", "find", "full_text", "html", "links", "lxml", "next","next_symbol", "page", "pq", "raw_html", "render", "search", "search_all", "session", "skip_anchors", "text", "url", "xpath"]

该函数只能输入大概内容,细节还是需要通过 help 函数查询,例如:

html 对象的方法包括

  • find:提供一个 css 选择器,返回一个元素列表;
  • xpath:提供一个 xpath 表达式,返回一个元素列表;
  • search: 根据传入的模板参数,查找 Element 对象;
  • search_all:同上,返回的全部数据;

html 对象的属性包括

  • links:返回页面所有链接;
  • absolute_links:返回页面所有链接的绝对地址;
  • base_url:页面的基准 URL;
  • htmlraw_htmltext:以 HTML 格式输入页面,输出未解析过的网页,提取页面所有文本;

有了上述内容铺垫之后,在进行 Python 爬虫的编写就会变的容易许多,requests-html 库将通过 3~4 个案例进行学习掌握,接下来进入第一个案例。

目标站点分析

本次要采集的目标网站为:http://www.world68.com/top.asp?t=5star&page=1,目标站点描述为【全球名站】。

在获取数据源发送请求前,忽然想起可以动态修改 user-agent,查阅该库源码发现,它只是使用了 fake_useragent 库来进行操作,并无太神奇的地方,所以可用可不用该内容。

DEFAULT_USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/603.3.8 (KHTML, like Gecko) Version/10.1.2 Safari/603.3.8"def user_agent(style=None) -> _UserAgent:    """Returns an apparently legit user-agent, if not requested one of a specific    style. Defaults to a Chrome-style User-Agent.    """    global useragent    if (not useragent) and style:        useragent = UserAgent()    return useragent[style] if style else DEFAULT_USER_AGENT

其余内容相对比较简单,页码规则如下:

http://www.world68.com/top.asp?t=5star&page=1http://www.world68.com/top.asp?t=5star&page=2

累计页数直接在底部进行了展示,可以设计为用户手动输入,即 input 函数实现。

目标数据存储网站名网站地址即可,基于此,开始编码。

编码时间

首先通过单线程实现 requests-html 的基本逻辑,注意到下述代码非常轻量,

from requests_html import HTMLSessionsession = HTMLSession()page_size = int(input("请输入总页码:"))for page in range(1, page_size + 1):    world = session.get(f"http://www.world68.com/top.asp?t=5star&page={page}")    world.encoding = "gb2312"    # world.html.encoding = "gb2312"    # print(world.text)    print("正在采集数据", world.url)    title_a = world.html.find("dl>dt>a")    for item in title_a:        name = item.text        url = item.attrs["href"]        with open("webs.txt", "a+", encoding="utf-8") as f:            f.write(f"{name},{url}/n")

上述代码重点部分说明如下:

  • world.encoding,设置了网页解析编码;
  • world.html.find("dl>dt>a") 通过 css 选择器,查找所有的网页标题元素;
  • item.text 提取网页标题内容;
  • item.attrs["href"] 获取元素属性,即网站域名。

运行效果如下所示,获取到的 3519 个站点,就不在提供了,简单运行 1 分钟代码,即可得到。

由于上述代码太少了,完全不够今日代码量,我们顺手将其修改为多线程形式。

import requests_htmlimport threadingimport timeimport fcntlclass MyThread(threading.Thread):    def __init__(self):        threading.Thread.__init__(self)    def run(self):        global page, lock, page_size        while True:            lock.acquire(True)            if page >= page_size:                lock.release()                break            else:                page += 1                lock.release()                requests_html.DEFAULT_ENCODING = "gb18030"                session = requests_html.HTMLSession()                print("正在采集第{}页".format(page), "*" * 50)                try:                    page_url = f"http://www.world68.com/top.asp?t=5star&page={page}"                    world = session.get(page_url, timeout=10)                    print("正在采集数据", world.url)                    # print(world.html)                    title_a = world.html.find("dl>dt>a")                    print(title_a)                    my_str = ""                    for item in title_a:                        name = item.text                        url = item.attrs["href"]                        my_str += f"{name.encode("utf-8").decode("utf-8")},{url}/n"                    with open("thread_webs.txt", "a+", encoding="utf-8") as f:                        fcntl.flock(f.fileno(), fcntl.LOCK_EX)  # 文件加锁                        f.write(f"{my_str}")                except Exception as e:                    print(e, page_url)if "__main__" == __name__:    page_size = int(input("请输入总页码:"))    page = 0    thread_list = []    # 获取开始时间    start = time.perf_counter()    lock = threading.Lock()    for i in range(1, 5):        t = MyThread()        thread_list.append(t)    for t in thread_list:        t.start()    for t in thread_list:        t.join()    # 获取时间间隔    elapsed = (time.perf_counter() - start)    print("程序运行完毕,总耗时为:", elapsed)

在正式进行编码之后,发现存在比较大的问题,编码问题,出现如下错误:

encoding error : input conversion failed due to input error, bytes 0x81 0xE3 0xD3 0xAAencoding error : input conversion failed due to input error, bytes 0x81 0xE3 0xD3 0xAAencoding error : input conversion failed due to input error, bytes 0x81 0xE3 0xD3 0xAAI/O error : encoder error

该错误在执行单线程时并未发生,但是当执行多线程时,异常开始出现,本问题在互联网上无解决方案,只能自行通过 requests-html 库的源码进行修改。

打开 requests_html.py 文件,将 417 行左右的代码进行如下修改:

def __init__(self, *, session: Union["HTMLSession", "AsyncHTMLSession"] = None, url: str = DEFAULT_URL, html: _HTML, default_encoding: str = DEFAULT_ENCODING, async_: bool = False) -> None:	# 修改本部分代码    # Convert incoming unicode HTML into bytes.    # if isinstance(html, str):    html = html.decode(DEFAULT_ENCODING,"replace")    super(HTML, self).__init__(        # Convert unicode HTML to bytes.        element=PyQuery(html)("html") or PyQuery(f"{html}")("html"),        html=html,        url=url,        default_encoding=default_encoding    )

代码 if isinstance(html, str): 用于判断 html 是否为 str,但是在实测过程中发现 html 类型,所以数据没有进行转码工作,故取消相关判断。

除此以外,通过输出 world.html.encoding 发现网页的编码不是 GB2312 ,而是 gb18030,所以通过下述代码进行了默认编码的设置。

requests_html.DEFAULT_ENCODING = "gb18030"

按照如上内容进行修改之后,代码可以正常运行,数据能正确的采集到。

本案例还新增了代码运行时长的计算,具体如下:

# 获取开始时间start = time.perf_counter()# 执行代码的部分# 获取时间间隔elapsed = (time.perf_counter() - start)print("程序运行完毕,总耗时为:", elapsed)

完整的代码运行效果如下所示:

收藏时间

代码仓库地址:https://codechina.csdn.net/hihell/python120,去给个关注或者 Star 吧。

数据没有采集完毕,想要的可以在评论区留言交流

今天是持续写作的第 212 / 365 天。
可以关注我,点赞我、评论我、收藏我啦。

更多精彩

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/119409.html

相关文章

  • Python 爬虫实战(二):使用 requests-html

    摘要:爬虫实战一使用和,我们使用了做网络请求,拿到网页数据再用解析,就在前不久,作者出了一个新库,,它可以用于解析文档的。是基于现有的框架等库进行了二次封装,更加方便开发者调用。参考今天用了一下库爬虫公众号我的公众号吴小龙同学,欢迎交流 Python 爬虫实战(一):使用 requests 和 BeautifulSoup,我们使用了 requests 做网络请求,拿到网页数据再用 Beaut...

    honmaple 评论0 收藏0
  • 这个男人让你的爬虫开发效率提升8倍

    摘要:提升倍虽是我胡诌的数据,开发效率的提升却是杠杠滴。而却不同,它提供了官方中文文档,其中包括了很清晰的快速上手和详尽的高级用法和接口指南。其他更多详细内容不多说了,中文官网地址,顺着看一遍,写一遍,你就掌握这个爬虫神器了。 他叫 Kenneth Reitz。现就职于知名云服务提供商 DigitalOcean,曾是云计算平台 Heroku 的 Python 架构师,目前 Github 上 ...

    Jackwoo 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<