CSS位置偏移反爬案例分析与爬取实战

  1. 案例

案例网址:https://antispider3.scrape.cener/,页面如下图所示:

Screenshot 2024-03-30 at 07.39.32

尝试用Selenium获取首页的页面源代码,并解析每个标题的内容:

from selenium import webdriver
from pyquery import PyQuery as pq
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.chrome.service import Service

options = webdriver.ChromeOptions()
services = Service('../Selenium/chromedriver')

browser = webdriver.Chrome(service=services, options=options)
browser.get('<https://antispider3.scrape.center/>')
WebDriverWait(browser, 10).until(EC.presence_of_all_elements_located((By.CSS_SELECTOR, '.item')))

html = browser.page_source
doc = pq(html)
names = doc('.item .name')
for name in names.items():
    print(name.text())
browser.close()

运行结果如下:

Wonder
清 白 家 风
篇 法 妃 老 的 上 宠 终 册 ) ( 结 下
士 为 己 ) 二 册 知 全 (
, 些 年 们 一 孩 我 的 那 起 女 追
非 我 倾 城 ( 全 三 册 )
朝 事 儿 明 些 那
的 你 忘 和 书 笑 我
全 第 波 集 小 一 卷 王
怦 然 动 心
龙枪编年史(全3册)
龙 枪 册 全 奇 ( ) 三 传
黎 明 之 街
其 知 认 理 学 心 示 启 及
银河帝国2:基地与帝国
银 河 帝 国 : 基 地
级 下 材 全 教 学 - 年 解 语 文 四 小
越界言论(第3卷)

结果中很多标题的文字顺序是乱的,例如《明朝那些事儿》对应的输出结果是“朝事儿明些那”,这是怎么回事?

  1. 排查

我们去浏览器里面研究一下源代码,如图所示:

Screenshot 2024-03-29 at 21.05.21

发现一个字对应一个span节点,这个节点本身的顺序也是乱的,所以用pyquery提取出来的标题内容乱序就不足为怪了。

源代码中的文字本身是乱的,那为什么在网页上看到的标题是正确的?这是因为网页本身利用CSS控制了文字的偏移位置,什么意思呢?观察下源代码:

<h3 data-v-7f1a77ef="" class="m-b-sm name">
<span data-v-7f1a77ef="" class="char" style="left: 80px;"></span>
<span data-v-7f1a77ef="" class="char" style="left: 16px;"></span>
<span data-v-7f1a77ef="" class="char" style="left: 0px;"></span>
<span data-v-7f1a77ef="" class="char" style="left: 48px;"></span>
<span data-v-7f1a77ef="" class="char" style="left: 32px;"></span>
<span data-v-7f1a77ef="" class="char" style="left: 64px;"></span>
  </h3>

可以发现,每个span节点都有一个style属性,表示CSS样式,left的取值各不相同。另外,在浏览器中观察一下每个span节点的完整样式,如图所示:

Screenshot 2024-03-29 at 21.21.56

span节点还有两个额外的样式,是display: inline-block和position:absolute,或者比较重要,代表绝对定位,设置这个样式后,就可以通过修改left的值控制span节点在页面中的偏移位置了,例如left:0px代表不偏移;left:16px代表从左边算起向右偏移16像素,于是节点就到了右边。源代码中,“明”子的偏移量是0,“朝”字的偏移量是16像素,“那”字的偏移量是32像素,依此类推,最终标题的视觉效果就变成了“明朝那些事儿”。

  1. 爬取

了解了基本原理后,只需要获取每个span节点的style属性,提取出偏移值,然后排序就可以得到最终结果了。先实现基本的提取方法:

from selenium import webdriver
from pyquery import PyQuery as pq
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.chrome.service import Service
import re

def parse_name(name_html):
    chars = name_html('.char')
    items = []
    for char in chars.items():
        items.append({
            'text': char.text().strip(),
            'left': int(re.search('(\\d+)px', char.attr('style')).group(1))
        })
    items = sorted(items, key=lambda x:x['left'], reverse=False)
    return ''.join([item.get('text') for item in items])

options = webdriver.ChromeOptions()
services = Service('../chromedriver')

browser = webdriver.Chrome(service=services, options=options)
browser.get('<https://antisipder3.scrape.center/>')
WebDriverWait(browser, 10).until(EC.presence_of_all_elements_located((By.CSS_SELECTOR, '.item')))
html = browser.page_source
doc = pq(html)
names = doc('.item .name')
for name_html in names.items():
    name = parse_name(name_html)
    print(name)
browser.close()

这里定义了一个parse_name方法,用来解析页面源代码得到最终的标题。它接收一个参数name_html,就是标题的HTML文本,类似这样:

<h3 data-v-7f1a77ef="" class="m-b-sm name">
<span data-v-7f1a77ef="" class="char" style="left: 80px;"></span>
<span data-v-7f1a77ef="" class="char" style="left: 16px;"></span>
<span data-v-7f1a77ef="" class="char" style="left: 0px;"></span>
<span data-v-7f1a77ef="" class="char" style="left: 48px;"></span>
<span data-v-7f1a77ef="" class="char" style="left: 32px;"></span>
<span data-v-7f1a77ef="" class="char" style="left: 64px;"></span>
  </h3>

在parse_name方法中,我们首先选取.char节点,将其赋值为chars变量,然后遍历chars变量,其中每个条目各自对应一个span节点,其内容类似于:

<span data-v-7f1a77ef="" class="char" style="left: 16px"></span>

在parse_name方法中,我们首先选取.char节点,将其赋值为chars变量,然后遍历chars变量, 其中每个条目各自对应一个span节点,其内容类似于:

<span data-v-7f1a77ef="" class="char" style="left: 16px"></span>

遍历过程中,提取了span节点的文本内容作为字典的text属性,还提取了style属性的内容,例如这里提取的是16px,并用正则表达式提取了其中的数值,这里是16,将其赋值为字典的left属性。

遍历结束后,items的结果类似下面这样:

[{'text': '些', 'left': 48}, {'text': '事', 'left': 64}, {'text': '儿', 'left': 80}, {'text': '那', 'left': 32}, {'text': '朝', 'left': 16}, {'text': '明', 'left': 0}]

面对这样的结果,怎么排序呢?直接调用sorted方法就行,它有两个参数,一个是key,用来指定根据什么排序,这里我们直接使用lambda表达式提取span节点的left属性,所以最终结果是根据left的值排序而得;另一个参数是reverse,用来指定排序方式,此处将其设置为False,表示从小到大排序。排序完的items变成了这样:

[{'text': '明', 'left': 0}, {'text': '朝', 'left': 16}, {'text': '那', 'left': 32}, {'text': '些', 'left': 48}, {'text': '事', 'left': 64}, {'text': '儿', 'left': 80}]

最后将其中的text值提取出来并拼接,就得到了最终结果:

清白家风
法老的宠妃终结篇(上下册)
士为知己(全二册)
那些年,我们一起追的女孩
非我倾城(全三册)
明朝那些事儿
我和你的笑忘书
王小波全集第一卷
怦然心动

龙枪传奇(全三册)
黎明之街
认知心理学及其启示

银河帝国:基地
小学教材全解-四年级语文下

更多体验可以在小蜜蜂AI获取,网址:https://zglg.work

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐