2015-12-21 15 views
17

Các Câu hỏi:Mở rộng selectors CSS trong BeautifulSoup

BeautifulSoup cung cấp một hỗ trợ rất hạn chế cho CSS selectors. Ví dụ: lớp giả được hỗ trợ duy nhất là nth-of-type và chỉ có thể chấp nhận các giá trị số - các đối số như even hoặc odd không được phép.

Có thể mở rộng BeautifulSoup bộ chọn CSS hoặc để nó sử dụng lxml.cssselect nội bộ dưới dạng cơ chế chọn CSS cơ bản không?


Chúng ta hãy nhìn vào một dụ trường hợp vấn đề/sử dụng. Xác định vị trí chỉ thậm chí hàng trong HTML sau:

<table> 
    <tr> 
     <td>1</td> 
    <tr> 
     <td>2</td> 
    </tr> 
    <tr> 
     <td>3</td> 
    </tr> 
    <tr> 
     <td>4</td> 
    </tr> 
</table> 

Trong lxml.htmllxml.cssselect, nó rất dễ dàng để làm qua :nth-of-type(even):

from lxml.html import fromstring 
from lxml.cssselect import CSSSelector 

tree = fromstring(data) 

sel = CSSSelector('tr:nth-of-type(even)') 

print [e.text_content().strip() for e in sel(tree)] 

Nhưng, trong BeautifulSoup:

print(soup.select("tr:nth-of-type(even)")) 

sẽ ném một lỗi:

NotImplementedError: Only numeric values are currently supported for the nth-of-type pseudo-class.


Lưu ý rằng chúng ta có thể workaround nó với .find_all():

print([row.get_text(strip=True) for index, row in enumerate(soup.find_all("tr"), start=1) if index % 2 == 0]) 

Trả lời

8

Sau khi kiểm tra mã nguồn, có vẻ như BeautifulSoup không cung cấp bất kỳ điểm nào thuận tiện trong giao diện của nó để mở rộng hoặc khỉ vá chức năng hiện có của mình trong vấn đề này. Sử dụng chức năng từ lxml là không thể vì BeautifulSoup chỉ sử dụng lxml trong khi phân tích cú pháp và sử dụng kết quả phân tích cú pháp để tạo các đối tượng tương ứng của riêng chúng từ chúng.Các đối tượng lxml không được lưu giữ và không thể truy cập được sau này.

Điều đó đang được nói, với sự quyết tâm đầy đủ và với khả năng linh hoạt và nội tâm của Python, mọi thứ đều có thể. Bạn có thể thay đổi phương pháp internals BeautifulSoup ngay cả ở thời gian chạy:

import inspect 
import re 
import textwrap 

import bs4.element 


def replace_code_lines(source, start_token, end_token, 
         replacement, escape_tokens=True): 
    """Replace the source code between `start_token` and `end_token` 
    in `source` with `replacement`. The `start_token` portion is included 
    in the replaced code. If `escape_tokens` is True (default), 
    escape the tokens to avoid them being treated as a regular expression.""" 

    if escape_tokens: 
     start_token = re.escape(start_token) 
     end_token = re.escape(end_token) 

    def replace_with_indent(match): 
     indent = match.group(1) 
     return textwrap.indent(replacement, indent) 

    return re.sub(r"^(\s+)({}[\s\S]+?)(?=^\1{})".format(start_token, end_token), 
        replace_with_indent, source, flags=re.MULTILINE) 


# Get the source code of the Tag.select() method 
src = textwrap.dedent(inspect.getsource(bs4.element.Tag.select)) 

# Replace the relevant part of the method 
start_token = "if pseudo_type == 'nth-of-type':" 
end_token = "else" 
replacement = """\ 
if pseudo_type == 'nth-of-type': 
    try: 
     if pseudo_value in ("even", "odd"): 
      pass 
     else: 
      pseudo_value = int(pseudo_value) 
    except: 
     raise NotImplementedError(
      'Only numeric values, "even" and "odd" are currently ' 
      'supported for the nth-of-type pseudo-class.') 
    if isinstance(pseudo_value, int) and pseudo_value < 1: 
     raise ValueError(
      'nth-of-type pseudo-class value must be at least 1.') 
    class Counter(object): 
     def __init__(self, destination): 
      self.count = 0 
      self.destination = destination 

     def nth_child_of_type(self, tag): 
      self.count += 1 
      if pseudo_value == "even": 
       return not bool(self.count % 2) 
      elif pseudo_value == "odd": 
       return bool(self.count % 2) 
      elif self.count == self.destination: 
       return True 
      elif self.count > self.destination: 
       # Stop the generator that's sending us 
       # these things. 
       raise StopIteration() 
      return False 
    checker = Counter(pseudo_value).nth_child_of_type 
""" 
new_src = replace_code_lines(src, start_token, end_token, replacement) 

# Compile it and execute it in the target module's namespace 
exec(new_src, bs4.element.__dict__) 
# Monkey patch the target method 
bs4.element.Tag.select = bs4.element.select 

This là một phần của mã đang được sửa đổi.

Tất nhiên, đây là tất cả mọi thứ nhưng thanh lịch và đáng tin cậy. Tôi không hình dung điều này đang được sử dụng nghiêm túc ở bất cứ nơi nào, bao giờ hết.

+0

Cảm ơn vì sự mạnh mẽ 'Tôi không hình dung điều này đang được sử dụng nghiêm túc ở bất cứ đâu, bao giờ hết.'! :) – alecxe

4

cách chính thức, BeautifulSoup không hỗ trợ tất cả các bộ chọn CSS.

Nếu python không phải là lựa chọn duy nhất, tôi khuyên bạn nên JSoup (tương đương với java này). Nó hỗ trợ tất cả các bộ chọn CSS.

  • Nó được mã nguồn mở (MIT giấy phép)
  • Cú pháp rất dễ dàng
  • Hỗ trợ tất cả các selectors css
  • thể span nhiều chủ đề quá để mở rộng quy mô
  • hỗ trợ API Giàu java để lưu trữ trong DB. Vì vậy, nó rất dễ dàng để tích hợp.

Cách khác thay thế nếu bạn vẫn muốn gắn bó với python, làm cho nó thực hiện jython.

http://jsoup.org/

https://github.com/jhy/jsoup/