リクエストに関する最初の知り合い-htmlライブラリ+I/ Oエラー:BUGを解決するための情報がないエンコーダーエラー、Pythonクローラーの30番目のケース

「オファーが届きました。友達を探して受け取りましょう。私は2022年の春の採用チェックインイベントに参加しています。クリックしてイベントの詳細を表示してください。」

このブログは「クローラー120ケース」の30番目のケースです。新しいクローラーフレームワークを学んでいrequests-htmlます。このフレームワークの作者はrequestsの作者なので、ブラインド推測は非常に便利です。

知識の伏線作業

requests-htmlモジュールはインストールして使用pip install requests-htmlできます。公式マニュアルクエリアドレス: requests-html.kennethreitz.org/、公式には中国語の直接翻訳はありません。取得プロセス中に、中国語マニュアルのバージョンが実際に見つかりました。記事の最後に記載されています。

ライブラリの公式の基本的な説明を見てみましょう。

  • JavaScriptの完全サポート!(JSの完全サポート、ここではマニュアルでも強調表示されています。初心者の場合は無視してかまいません)
  • CSSセレクター(別名jQueryスタイル、PyQueryのおかげで)(pyqueryライブラリと統合され、cssセレクターをサポートします)
  • XPathセレクター、心の弱い人向け(XPathセレクターをサポート)
  • 模擬ユーザーエージェント(実際のWebブラウザーのように)(模擬UAデータ。これは良いことです)
  • リダイレクトの自動フォロー
  • 接続プールとCookieの永続性。(持久性クッキー)
  • リクエストは、魔法の構文解析能力を備えた、あなたが知っていて大好きな体験をします。

Python3.6のみがサポートされています。Python3.6のみがサポートされており、3.6以降のバージョンも引き続き使用できます。

ライブラリを簡単に使用するためのコードは次のようになります。

from requests_html import HTMLSession
session = HTMLSession()

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

print(r)
复制代码

requests_html最初HTMLSessionにライブラリからクラスをインポートし、次にそれをインスタンス化し、そのgetメソッド、リクエストを送信し、r出力を取得します<Response [200]>。次に、組み込みの解析ライブラリを使用してデータを解析できます。

ライブラリは解析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']
复制代码

この関数は一般的なコンテンツのみを入力でき、ヘルプ関数を介して詳細を照会する必要があります。次に例を示します。

htmlオブジェクトのメソッドは次のとおりです。

  • find:要素のリストを返すcssセレクターを提供します。
  • xpath:要素のリストを返すxpath式を提供します。
  • search:渡されたテンプレートパラメータに従ってElementオブジェクトを検索します。
  • search_all:上記と同じように、すべてのデータが返されます。

htmlオブジェクトのプロパティには次のものがあります。

  • links:ページ上のすべてのリンクを返します。
  • absolute_links:ページ上のすべてのリンクの絶対アドレスを返します。
  • base_url:ページのベースURL。
  • html、、:ページをHTML形式で入力し、解析されていないWebページを出力して、ページのすべてのテキストを抽出しますraw_htmltext

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

目标站点分析

本次要采集的目标网站为:www.world68.com/top.asp?t=5… リクエストに関する最初の知り合い-htmlライブラリ+I/ Oエラー:BUGを解決するための情報がないエンコーダーエラー、Pythonクローラーの30番目のケース 在获取数据源发送请求前,忽然想起可以动态修改 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 分钟代码,即可得到。 リクエストに関する最初の知り合い-htmlライブラリ+I/ Oエラー:BUGを解決するための情報がないエンコーダーエラー、Pythonクローラーの30番目のケース 由于上述代码太少了,完全不够今日代码量,我们顺手将其修改为多线程形式。

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

コードif isinstance(html, str):htmlそれがそうであるかどうかを判断するために使用されますがstr、実際の測定プロセスでは、それhtml<class 'bytes'>タイプであることが判明したため、データはトランスコードされないため、関連する判断はキャンセルされます。

また、出力から、Webページのエンコーディングはではなく、であることがworld.html.encodingわかります。したがって、デフォルトのエンコーディングは次のコードで設定されます。GB2312gb18030

requests_html.DEFAULT_ENCODING = "gb18030"
复制代码

上記の内容に従って変更した後、コードは正常に実行され、データは正しく収集されます。

この場合、次のようにコード実行時間の計算も追加されます。

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

完全なコード実行効果は次のとおりです。

リクエストに関する最初の知り合い-htmlライブラリ+I/ Oエラー:BUGを解決するための情報がないエンコーダーエラー、Pythonクローラーの30番目のケース

収集時間

コードリポジトリアドレス:codechina.csdn.net/hihell/pyth…、フォローまたはスターを付けてください。

==データは収集されていません。通信したい場合は、コメント領域にメッセージを残すことができます==

今日は連続執筆の212/365日です。あなたは私をフォローすることができます、私のように、私にコメントし、私をブックマークします。

おすすめ

転載: juejin.im/post/7079964452585521159