使用 BS4 进行抓取,但 HTML 在解析时搞砸了

Scraping with BS4 but HTML gets messed up when parsed

我在使用 BeautifulSoup4 和 Python3 抓取网站时遇到问题。我正在使用 dryscrape 来获取 HTML,因为它需要启用 JavaScript 才能显示(但据我所知,它从未在页面本身中使用过)。

这是我的代码:

from bs4 import BeautifulSoup
import dryscrape

productUrl = "https://www.mercadona.es/detall_producte.php?id=32009"
session = dryscrape.Session()
session.visit(productUrl)
response = session.body()
soup = BeautifulSoup(response, "lxml")
container1 = soup.find("div","contenido").find("dl").find_all("dt")
container3 = soup.find("div","contenido").find_all("td")

现在我想阅读 container3 内容,但是:

type(container3)

Returns:

bs4.element.ResultSet

type(container1)相同,但长度为0!

所以我想知道在寻找我的 <td> 标签之前我得到了什么 container3,所以我把它写到一个文件中。

container3 = soup.find("div","contenido")
soup_file.write(container3.prettify())

并且,这是该文件的 link:https://pastebin.com/xc22fefJ

就在我要抓取 table 之前,它变得一团糟。我不明白为什么,查看 Firefox 的 URL 源代码一切看起来都很好。

这是更新后的解决方案:

url = 'https://www.mercadona.es/detall_producte.php?id=32009'

rh = {
    'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
    'Accept-Encoding': 'gzip, deflate, br',
    'Accept-Language': 'en-US,en;q=0.9',
    'Connection': 'keep-alive',
    'Host': 'www.mercadona.es',
    'Upgrade-Insecure-Requests': '1',
    'User-Agent': 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36'
}

s = requests.session()
r = s.get(url, headers = rh)

对此的响应为您提供 Please enable JavaScript to view the page content. 消息。但是,它也包含浏览器使用javascript发送的必要hidden数据,可以从开发者工具的网络选项卡中看到。

TS015fc057_id: 3
TS015fc057_cr: a57705c08e49ba7d51954bea1cc9bfce:jlnk:l8MH0eul:1700810263
TS015fc057_76: 0
TS015fc057_86: 0
TS015fc057_md: 1
TS015fc057_rf: 0
TS015fc057_ct: 0
TS015fc057_pd: 0

其中,第二个(长字符串)由 javascript 生成。我们可以使用像 js2py 这样的库来 运行 代码,这将 return 请求中传递的所需字符串。

soup = BeautifulSoup(r.content, 'lxml')
script = soup.find_all('script')[1].text

js_code = re.search(r'.*(function challenge.*crc;).*', script, re.DOTALL).groups()[0] + '} challenge();'
js_code = js_code.replace('document.forms[0].elements[1].value=', 'return ')

hidden_inputs = soup.find_all('input')
hidden_inputs[1]['value'] = js2py.eval_js(js_code)

fd = {i['name']: i['value'] for i in hidden_inputs}

rh = {
    'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
    'Referer': 'https://www.mercadona.es/detall_producte.php?id=32009',
    'Accept-Encoding': 'gzip, deflate, br',
    'Accept-Language': 'en-US,en;q=0.9',
    'Connection': 'keep-alive',
    'Content-Length': '188',
    'Content-Type': 'application/x-www-form-urlencoded',
    'Cache-Control': 'max-age=0',
    'Host': 'www.mercadona.es',
    'Origin': 'https://www.mercadona.es',
    'Upgrade-Insecure-Requests': '1',
    'User-Agent': 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36'
}

# NOTE: the next one is a POST request, as opposed to the GET request sent before
r = s.post(url, headers = rh, data = fd)
soup = BeautifulSoup(r.content, 'lxml')

结果如下:

>>> len(soup.find('div', 'contenido').find_all('td'))
70
>>> len(soup.find('div', 'contenido').find('dl').find_all('dt'))
8

编辑

显然,javascript 代码只需要 运行 一次。生成的数据可用于多个请求,如下所示:

for i in range(32007, 32011):
    r = s.post(url[:-5] + str(i), headers = rh, data = fd)
    soup = BeautifulSoup(r.content, 'lxml')
    print(soup.find_all('dd')[1].text)

结果:

Manzana y plátano 120 g
Manzana y plátano 720g (6x120) g
Fresa Plátano 120 g
Fresa Plátano 720g (6x120g)