Première connaissance de la bibliothèque requests-html + erreur d'E/S : erreur d'encodeur sans information pour résoudre BUG, le 30ème cas de crawler Python

"L'offre arrive, cherchez des amis à récupérer ! Je participe à l'événement de vérification du recrutement du printemps 2022, cliquez pour voir les détails de l'événement ."

Ce blog est le 30ème cas de "Crawler 120 Cases". J'apprends un nouveau framework de robot d'exploration requests-html. L'auteur de ce framework est requestsl' auteur de , donc deviner à l'aveugle est très utile.

Connaissances préfigurant le travail

requests-htmlLe module pip install requests-htmlpeut être . L'adresse de requête du manuel officiel : requests-html.kennethreitz.org/ , l'officiel n'a pas de traduction directe en chinois. Au cours du processus de récupération, une version du manuel chinois a en effet été trouvée, qui est fournis en fin d'article.

Jetons un coup d'œil à la description de base officielle de la bibliothèque :

  • Prise en charge complète de JavaScript ! (Prise en charge complète de JS, ici le manuel le met également en évidence, vous pouvez l'ignorer pour les débutants)
  • Sélecteurs CSS (alias style jQuery, grâce à PyQuery) (intégré à la bibliothèque pyquery, prend en charge les sélecteurs CSS)
  • Sélecteurs XPath, pour les âmes sensibles (prend en charge les sélecteurs XPath)
  • Agent utilisateur simulé (comme un vrai navigateur Web) (simuler les données UA, ce qui est bien)
  • Suivi automatique des redirections .
  • Mise en commun des connexions et persistance des cookies. (持久性 COOKIE)
  • L'expérience Requests que vous connaissez et aimez, avec des capacités d'analyse magiques.

Seul Python 3.6 est pris en charge Seul Python 3.6 est pris en charge et il s'avère que les versions supérieures à 3.6 peuvent toujours être utilisées.

Pour une utilisation simple de la librairie, le code ressemble à ceci :

from requests_html import HTMLSession
session = HTMLSession()

r = session.get('https://python.org/')

print(r)
复制代码

requests_htmlImportez d' abord HTMLSessionla classe à partir de la bibliothèque, puis instanciez-la, appelez sa getméthode , envoyez une requête et obtenez la rsortie <Response [200]>, puis vous pouvez utiliser la bibliothèque d'analyse intégrée pour analyser les données.

Étant donné que la bibliothèque est un htmlobjet , vous pouvez voir htmlquelles méthodes et propriétés l'objet correspondant contient.

Découvrez la dirfonction .

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']
复制代码

Cette fonction ne peut entrer que le contenu général, et les détails doivent encore être interrogés via la fonction d'aide, par exemple :

Les méthodes de l'objet html incluent

  • find: Fournit un sélecteur css qui renvoie une liste d'éléments ;
  • xpath: fournit une expression xpath qui renvoie une liste d'éléments ;
  • search: recherchez l'objet Element en fonction des paramètres de modèle transmis ;
  • search_all: Comme ci-dessus, toutes les données sont renvoyées ;

Les propriétés de l'objet html incluent

  • links: renvoie tous les liens de la page ;
  • absolute_links: Renvoie l'adresse absolue de tous les liens de la page ;
  • base_url: L'URL de base de la page ;
  • html, raw_html,text : saisissez la page au format HTML, affichez la page Web non analysée et extrayez tout le texte de la page ;

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

目标站点分析

本次要采集的目标网站为:www.world68.com/top.asp?t=5… Première connaissance de la bibliothèque requests-html + erreur d'E/S : erreur d'encodeur sans information pour résoudre BUG, ​​le 30ème cas de crawler Python 在获取数据源发送请求前,忽然想起可以动态修改 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=1
http://www.world68.com/top.asp?t=5star&page=2
复制代码

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

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

编码时间

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

from requests_html import HTMLSession

session = 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 分钟代码,即可得到。 Première connaissance de la bibliothèque requests-html + erreur d'E/S : erreur d'encodeur sans information pour résoudre BUG, ​​le 30ème cas de crawler Python 由于上述代码太少了,完全不够今日代码量,我们顺手将其修改为多线程形式。

import requests_html
import threading
import time
import fcntl


class 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 0xAA
encoding error : input conversion failed due to input error, bytes 0x81 0xE3 0xD3 0xAA
encoding error : input conversion failed due to input error, bytes 0x81 0xE3 0xD3 0xAA
I/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'),
        html=html,
        url=url,
        default_encoding=default_encoding
    )
复制代码

Le code if isinstance(html, str):est utilisé pour juger htmls'il s'agit strde , mais dans le processus de mesure réel, il s'avère qu'il htmls'agit d'un <class 'bytes'>type, de sorte que les données ne sont pas transcodées, de sorte que le jugement pertinent est annulé.

De plus, à travers la sortie, on world.html.encodingconstate que l'encodage de la page Web n'est pas GB2312, mais gb18030, donc l'encodage par défaut est défini par le code suivant.

requests_html.DEFAULT_ENCODING = "gb18030"
复制代码

Après modification en fonction du contenu ci-dessus, le code peut s'exécuter normalement et les données peuvent être collectées correctement.

Ce cas ajoute également le calcul du temps d'exécution du code, comme suit :

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

L'effet d'exécution complet du code est le suivant :

Première connaissance de la bibliothèque requests-html + erreur d'E/S : erreur d'encodeur sans information pour résoudre BUG, ​​le 30ème cas de crawler Python

Heure de collecte

Adresse du référentiel de code : codechina.csdn.net/hihell/pyth… , donnez-nous un suivi ou une étoile.

==Les données n'ont pas été collectées, vous pouvez laisser un message dans l'espace commentaire si vous souhaitez communiquer==

Aujourd'hui est le jour 212/365 d'écriture continue. Vous pouvez me suivre, me liker, me commenter, me mettre en signet.

Je suppose que tu aimes

Origine juejin.im/post/7079964452585521159
conseillé
Classement