【问题标题】:Recursively parse all category links and get all products递归解析所有类别链接,获取所有产品
【发布时间】:2017-11-29 17:00:54
【问题描述】:

我一直在玩网络抓取(使用 Python 3.6.2 进行这个练习),我觉得我有点失去它了。给定this 示例链接,这就是我想要做的:

首先,如您所见,页面上有多个类别。单击上面的每个类别都会给我其他类别,然后是其他类别,依此类推,直到我到达产品页面。所以我必须深入 x 次。我认为递归会帮助我实现这一点,但在某处我做错了。

代码:

在这里,我将解释我解决问题的方式。首先,我创建了一个会话和一个简单的通用函数,它将返回一个 lxml.html.HtmlElement 对象:

from lxml import html
from requests import Session


HEADERS = {
    "User-Agent": "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) "
                  "Chrome/62.0.3202.94 Safari/537.36"
}
TEST_LINK = 'https://www.richelieu.com/us/en/category/custom-made-cabinet-doors-and-drawers/1000128'

session_ = Session()


def get_page(url):
    page = session_.get(url, headers=HEADERS).text
    return html.fromstring(page)

然后,我想我还需要另外两个函数:

  • 一个获取类别链接
  • 另一个获取产品链接

为了区分一个和另一个,我发现只有在类别页面上,每次都有一个包含CATEGORIES的标题,所以我使用了:

def read_categories(page):
    categs = []
    try:
        if 'CATEGORIES' in page.xpath('//div[@class="boxData"][2]/h2')[0].text.strip():
            for a in page.xpath('//*[@id="carouselSegment2b"]//li//a'):
                categs.append(a.attrib["href"])
            return categs
        else:
            return None
    except Exception:
        return None


def read_products(page):
    return [
        a_tag.attrib["href"]
        for a_tag in page.xpath("//ul[@id='prodResult']/li//div[@class='imgWrapper']/a")
    ]

现在,唯一剩下的就是递归部分,我确定我做错了什么:

def read_all_categories(page):
    cat = read_categories(page)
    if not cat:
        yield read_products(page)
    else:
        yield from read_all_categories(page)


def main():
    main_page = get_page(TEST_LINK)

    for links in read_all_categories(main_page):
        print(links)

所有代码放在一起:

from lxml import html
from requests import Session


HEADERS = {
    "User-Agent": "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) "
                  "Chrome/62.0.3202.94 Safari/537.36"
}
TEST_LINK = 'https://www.richelieu.com/us/en/category/custom-made-cabinet-doors-and-drawers/1000128'

session_ = Session()


def get_page(url):
    page = session_.get(url, headers=HEADERS).text
    return html.fromstring(page)


def read_categories(page):
    categs = []
    try:
        if 'CATEGORIES' in page.xpath('//div[@class="boxData"][2]/h2')[0].text.strip():
            for a in page.xpath('//*[@id="carouselSegment2b"]//li//a'):
                categs.append(a.attrib["href"])
            return categs
        else:
            return None
    except Exception:
        return None


def read_products(page):
    return [
        a_tag.attrib["href"]
        for a_tag in page.xpath("//ul[@id='prodResult']/li//div[@class='imgWrapper']/a")
    ]


def read_all_categories(page):
    cat = read_categories(page)
    if not cat:
        yield read_products(page)
    else:
        yield from read_all_categories(page)


def main():
    main_page = get_page(TEST_LINK)

    for links in read_all_categories(main_page):
        print(links)


if __name__ == '__main__':
    main()

有人可以为我指出关于递归函数的正确方向吗?

【问题讨论】:

  • 我建议使用scrapy 进行网络爬取,特别是针对您的问题,我会使用CrawlSpider,您只需定义项目页面的结构,并使用正则表达式来查找和关注类别.
  • 我知道。但在进入 Scrapy 之前,我想先了解一下基础知识 :)
  • @eLRuLL 做print(page.xpath('//div[@class="boxData"][2]/h2')[0].text.strip()) 确实返回了我的期望。 (我编辑了第一个代码,因为我错过了索引)

标签: python python-3.x xpath web-scraping lxml


【解决方案1】:

我将如何解决这个问题:

from lxml import html as html_parser
from requests import Session

HEADERS = {
    "User-Agent": "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 "
                  "(KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36"
}

def dig_up_products(url, session=Session()):
    html = session.get(url, headers=HEADERS).text
    page = html_parser.fromstring(html)

    # if it appears to be a categories page, recurse
    for link in page.xpath('//h2[contains(., "CATEGORIES")]/'
                           'following-sibling::div[@id="carouselSegment1b"]//li//a'):
        yield from dig_up_products(link.attrib["href"], session)

    # if it appears to be a products page, return the links
    for link in page.xpath('//ul[@id="prodResult"]/li//div[@class="imgWrapper"]/a'):
        yield link.attrib["href"]

def main():
    start = 'https://www.richelieu.com/us/en/category/custom-made-cabinet-doors-and-drawers/1000128'

    for link in dig_up_products(start):
        print(link)

if __name__ == '__main__':
    main()

对空的 XPath 表达式结果进行迭代没有任何问题,因此您可以简单地将两种情况(类别页面/产品页面)放入同一个函数中,只要 XPath 表达式足够具体以识别每种情况。

【讨论】:

  • 这看起来很棒,感谢您抽出宝贵时间查看!但令人惊讶的是,它没有打印任何东西......
  • 恐怕我无法测试它,我的工作场所现在有一些代理问题,所以 Python 无法获取任何页面。您必须将其作为调试的基础。 :) XPath 很可能是不正确的。如果您发现缺少的内容,请直接编辑答案,或发表评论。
  • 是的,我发现了错误,显然正确的 xpath 是://h2[contains(., "CATEGORIES")]/following-sibling::*[@id="carouselSegment2b"]//li//a。再次感谢!
  • dig_up_products()中还有一个案例。当我到达产品页面时(所以第二个 for 循环),它可能恰好是超过一页的产品(所以它会分页)。我发现使用这个 xpath://*[@id="ts_resultList"]/div/nav/ul/li[last()]/a/@href 将返回其他页面的列表。我不确定如何将其集成到上述功能中。请帮忙?
  • 我想你会自己解决这个问题的。你需要的所有东西都已经在那里了。我不会为你写下来,对不起。 :) 当你自己达到 aha!-moment 时,它会更有教育意义。
【解决方案2】:

您也可以这样做以使您的脚本稍微简洁。我使用lxml 库和css selector 来完成这项工作。该脚本将解析category下的所有链接并寻找死胡同,当它出现时,它会从那里解析标题并一遍又一遍地做所有的事情,直到所有的链接都用尽。

from lxml.html import fromstring
import requests

def products_links(link):
    res = requests.get(link, headers={"User-Agent": "Mozilla/5.0"})
    page = fromstring(res.text)

    try:
        for item in page.cssselect(".contentHeading h1"): #check for the match available in target page
            print(item.text)
    except:
        pass

    for link in page.cssselect("h2:contains('CATEGORIES')+[id^='carouselSegment'] .touchcarousel-item a"):
        products_links(link.attrib["href"])

if __name__ == '__main__':

    main_page = 'https://www.richelieu.com/us/en/category/custom-made-cabinet-doors-and-drawers/1000128'
    products_links(main_page)

部分结果:

BRILLANTÉ DOORS
BRILLANTÉ DRAWER FRONTS
BRILLANTÉ CUT TO SIZE PANELS
BRILLANTÉ EDGEBANDING
LACQUERED ZENIT DOORS
ZENIT CUT-TO-SIZE PANELS
EDGEBANDING
ZENIT CUT-TO-SIZE PANELS

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-09-03
    • 2017-04-01
    • 2021-06-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多