파이썬과 BeautifulSoup을 사용하여 웹 페이지에서 링크를 검색


답변:


193

BeautifulSoup의 SoupStrainer 클래스를 사용하는 짧은 스 니펫은 다음과 같습니다.

import httplib2
from bs4 import BeautifulSoup, SoupStrainer

http = httplib2.Http()
status, response = http.request('http://www.nytimes.com')

for link in BeautifulSoup(response, parse_only=SoupStrainer('a')):
    if link.has_attr('href'):
        print(link['href'])

BeautifulSoup 문서는 실제로 매우 훌륭하며 여러 가지 일반적인 시나리오를 다룹니다.

https://www.crummy.com/software/BeautifulSoup/bs4/doc/

편집 : 미리 구문 분석하는 것을 알고 있다면 SoupStrainer 클래스가 조금 더 효율적이기 때문에 (메모리 및 속도면에서) 효율적입니다.


13
+1, 수프 스트레이너를 사용하는 것은 좋은 아이디어입니다.
Evan Fosmark

4
헤드 업 :/usr/local/lib/python2.7/site-packages/bs4/__init__.py:128: UserWarning: The "parseOnlyThese" argument to the BeautifulSoup constructor has been renamed to "parse_only."
BenDundee

27
BeautifulSoup의 버전 3.2.1에는 없습니다 has_attr. 대신 나는 뭔가라는 has_key것이 있고 작동합니다.

2
python3에 대한 업데이트
john doe

7
bs4에서 BeautifulSoup을 가져옵니다. (BeautifulSoup에서 가져 오지 않음 BeautifulSoup ..) 수정이 필요했습니다.
Rishabh Agrahari

67

서버에서 제공하는 인코딩을 사용하여 BeautifulSoup 4 버전을 완성하기 위해 다음을 수행하십시오.

from bs4 import BeautifulSoup
import urllib.request

parser = 'html.parser'  # or 'lxml' (preferred) or 'html5lib', if installed
resp = urllib.request.urlopen("http://www.gpsbasecamp.com/national-parks")
soup = BeautifulSoup(resp, parser, from_encoding=resp.info().get_param('charset'))

for link in soup.find_all('a', href=True):
    print(link['href'])

또는 Python 2 버전 :

from bs4 import BeautifulSoup
import urllib2

parser = 'html.parser'  # or 'lxml' (preferred) or 'html5lib', if installed
resp = urllib2.urlopen("http://www.gpsbasecamp.com/national-parks")
soup = BeautifulSoup(resp, parser, from_encoding=resp.info().getparam('charset'))

for link in soup.find_all('a', href=True):
    print link['href']

그리고 사용 버전 requests라이브러리 로 작성, 파이썬 2와 3 모두에서 작동합니다 :

from bs4 import BeautifulSoup
from bs4.dammit import EncodingDetector
import requests

parser = 'html.parser'  # or 'lxml' (preferred) or 'html5lib', if installed
resp = requests.get("http://www.gpsbasecamp.com/national-parks")
http_encoding = resp.encoding if 'charset' in resp.headers.get('content-type', '').lower() else None
html_encoding = EncodingDetector.find_declared_encoding(resp.content, is_html=True)
encoding = html_encoding or http_encoding
soup = BeautifulSoup(resp.content, parser, from_encoding=encoding)

for link in soup.find_all('a', href=True):
    print(link['href'])

soup.find_all('a', href=True)호출은 속성 <a>이있는 모든 요소를 찾습니다 href. 속성이없는 요소는 건너 뜁니다.

BeautifulSoup 3는 2012 년 3 월 개발을 중단했습니다. 새로운 프로젝트는 항상 BeautifulSoup 4를 사용해야합니다.

바이트 에서 BeautifulSoup으로 HTML 디코딩을 남겨 두어야합니다 . 디코딩에 도움을주기 위해 HTTP 응답 헤더에있는 문자 세트를 BeautifulSoup에 알릴 수 있지만, 이는 잘못된 것으로 HTML 자체에 있는 헤더 정보 와 충돌 할 수 있으므로 <meta>위의 내용은 BeautifulSoup 내부 클래스 메소드 EncodingDetector.find_declared_encoding()를 사용하여 이러한 임베디드 인코딩 힌트는 잘못 구성된 서버보다 우선합니다.

requests1, response.encoding라틴어 1 속성 기본값 응답이있는 경우 text/*에는 characterset가 반환되지 않은 경우에도, MIME 형식을. 이는 HTTP RFC와 일치하지만 HTML 구문 분석과 함께 사용하면 고통스럽기 때문에 charsetContent-Type 헤더에 no 가 설정되어 있으면 해당 속성을 무시해야합니다 .


bs4 용 StrainedSoup과 같은 것이 있습니까? (지금은 필요하지 않지만 궁금한 점이 있다면 추가하고 싶을 수도 있습니다)
Antti Haapala

@AnttiHaapala : 그러세요 SoupStrainer? 그것은 여전히 프로젝트의 일부입니다, 어디 가지 않았다 .
Martijn Pieters

이 코드가 "features ="를 BeautifulSoup 생성자에 전달하지 않는 이유가 있습니까? BeautifulSoup은 기본 파서 사용에 대한 경고를 표시합니다.
MikeB

1
@ MikeB :이 대답을 쓸 때 BeautifulSoup은 아직 경고를하지 않았다면 경고를 올리지 않았습니다.
Martijn Pieters

50

다른 사람들은 BeautifulSoup을 추천했지만 lxml 을 사용하는 것이 훨씬 좋습니다 . 이름에도 불구하고 HTML 구문 분석 및 스크랩을위한 것입니다. BeautifulSoup보다 훨씬 빠르며, BeautifulSoup (명예를 주장하는 것)보다 "깨진"HTML을 더 잘 처리합니다. lxml API를 배우고 싶지 않은 경우 BeautifulSoup에 대한 호환성 API도 있습니다.

Ian Blicking이 동의합니다 .

Google App Engine을 사용하거나 순수하게 Python이 아닌 것을 허용하지 않는 한 BeautifulSoup을 더 이상 사용할 이유가 없습니다.

lxml.html은 CSS3 선택자를 지원하므로 이런 종류의 작업은 간단합니다.

lxml 및 xpath의 예는 다음과 같습니다.

import urllib
import lxml.html
connection = urllib.urlopen('http://www.nytimes.com')

dom =  lxml.html.fromstring(connection.read())

for link in dom.xpath('//a/@href'): # select the url in href for all a tags(links)
    print link

23
BeautifulSoup 4는 lxml설치된 경우 기본 파서로 사용됩니다.
Martijn Pieters

28
import urllib2
import BeautifulSoup

request = urllib2.Request("http://www.gpsbasecamp.com/national-parks")
response = urllib2.urlopen(request)
soup = BeautifulSoup.BeautifulSoup(response)
for a in soup.findAll('a'):
  if 'national-park' in a['href']:
    print 'found a url with national-park in the link'

이것은 내 코드와 관련된 문제를 해결했습니다. 감사합니다!
RJ

10

다음 코드를 사용하여 웹 페이지에서 사용할 수있는 모든 링크를 검색하는 것입니다 urllib2BeautifulSoup4:

import urllib2
from bs4 import BeautifulSoup

url = urllib2.urlopen("http://www.espncricinfo.com/").read()
soup = BeautifulSoup(url)

for line in soup.find_all('a'):
    print(line.get('href'))

8

후드 아래에서 BeautifulSoup은 이제 lxml을 사용합니다. 요청, lxml 및 목록 이해는 범인 콤보를 만듭니다.

import requests
import lxml.html

dom = lxml.html.fromstring(requests.get('http://www.nytimes.com').content)

[x for x in dom.xpath('//a/@href') if '//' in x and 'nytimes.com' not in x]

목록 comp에서 "if '//'및 'url.com'x in x"는 사이트의 '내부'탐색 URL 등의 URL 목록을 제거하는 간단한 방법입니다.


1
다시 게시하는 경우 원래 게시물에 다음이 포함되지 않습니다. 1. 요청 2.list comp 3. 사이트 내부 및 정크 링크를 제거하는 논리 ?? 두 게시물의 결과를 시도하고 비교하십시오. 내 목록 comp는 정크 링크를 제거하는 데 놀랍도록 훌륭한 일을합니다.
cheekybastard

OP는 해당 기능을 요청하지 않았으며 요청한 부분은 이미 게시 한 것과 동일한 방법으로 게시 및 해결되었습니다. 그러나 목록 이해가 그러한 기능을 원하는 사람들에게 가치를 더하고 게시물 본문에서 명시 적으로 언급하므로 다운 투표를 제거하겠습니다. 또한, 당신은 담당자를 사용할 수 있습니다 :)
dotancohen

4

B.soup과 정규 표현식없이 링크를 얻기 위해 :

import urllib2
url="http://www.somewhere.com"
page=urllib2.urlopen(url)
data=page.read().split("</a>")
tag="<a href=\""
endtag="\">"
for item in data:
    if "<a href" in item:
        try:
            ind = item.index(tag)
            item=item[ind+len(tag):]
            end=item.index(endtag)
        except: pass
        else:
            print item[:end]

보다 복잡한 작업을 위해서는 물론 BSoup이 선호됩니다.


7
그리고 경우, 예를 들어, 뭔가 inbetween 거기 <a와는 href? rel="nofollow"아니면 onclick="..."새로운 줄을 말 하거나 심지어? stackoverflow.com/questions/1732348/…
dimo414

이것으로 일부 링크 만 필터링하는 방법이 있습니까? 예를 들어 링크에 "Episode"가있는 링크 만 원하십니까?
nwgat

4

이 스크립트는 원하는 것을 수행하지만 절대 링크에 대한 상대 링크도 해결합니다.

import urllib
import lxml.html
import urlparse

def get_dom(url):
    connection = urllib.urlopen(url)
    return lxml.html.fromstring(connection.read())

def get_links(url):
    return resolve_links((link for link in get_dom(url).xpath('//a/@href')))

def guess_root(links):
    for link in links:
        if link.startswith('http'):
            parsed_link = urlparse.urlparse(link)
            scheme = parsed_link.scheme + '://'
            netloc = parsed_link.netloc
            return scheme + netloc

def resolve_links(links):
    root = guess_root(links)
    for link in links:
        if not link.startswith('http'):
            link = urlparse.urljoin(root, link)
        yield link  

for link in get_links('http://www.google.com'):
    print link

이것은 ti가 의도 한 바를 수행하지 않습니다. resolve_links ()에 루트가 없으면 URL을 반환하지 않습니다.
MikeB

4

모든 링크를 찾으려면이 예제에서 urllib2 모듈을 re.module과 함께 사용합니다. * re 모듈에서 가장 강력한 기능 중 하나는 "re.findall ()"입니다. re.search ()는 패턴의 첫 번째 일치 항목을 찾는 데 사용되는 반면 re.findall ()은 모든 일치 항목을 찾아 문자열 목록으로 반환합니다. 각 문자열은 하나의 일치 항목을 나타냅니다 *

import urllib2

import re
#connect to a URL
website = urllib2.urlopen(url)

#read html code
html = website.read()

#use re.findall to get all the links
links = re.findall('"((http|ftp)s?://.*?)"', html)

print links

3

정규식을 사용하지 않는 이유 :

import urllib2
import re
url = "http://www.somewhere.com"
page = urllib2.urlopen(url)
page = page.read()
links = re.findall(r"<a.*?\s*href=\"(.*?)\".*?>(.*?)</a>", page)
for link in links:
    print('href: %s, HTML text: %s' % (link[0], link[1]))

1
나는 이것을 이해할 수 있기를 원합니다. 무엇을 (r"<a.*?\s*href=\"(.*?)\".*?>(.*?)</a>", page)의미 하는지 효율적으로 찾을 수 있습니까? 감사!
user1063287

9
정말 나쁜 생각입니다. 모든 곳에서 HTML이 깨졌습니다.
Ufoguy 2019 년

2
정규 표현식을 사용하여 html을 구문 분석하지 않는 이유 : stackoverflow.com/questions/1732348/…
allcaps

@ user1063287, 웹에는 정규식 자습서가 가득합니다. 부부를 읽는 것이 좋습니다. RE가 실제로 복잡해 질 수 있지만 요청하는 것은 매우 기본적입니다.
Alexis

3

링크는 다양한 속성 내에있을 수 있으므로 해당 속성 목록을 전달하여 선택할 수 있습니다.

예를 들어, src 및 href 속성을 사용합니다 (여기서 starts with ^ 연산자를 사용하여 이러한 속성 값 중 하나가 http로 시작하도록 지정합니다). 필요에 따라이를 조정할 수 있습니다.

from bs4 import BeautifulSoup as bs
import requests
r = requests.get('https://stackoverflow.com/')
soup = bs(r.content, 'lxml')
links = [item['href'] if item.get('href') is not None else item['src'] for item in soup.select('[href^="http"], [src^="http"]') ]
print(links)

속성 = 값 선택기

[attr ^ = 값]

속성 이름이 attr이고 값 앞에 값이 붙는 요소 이름을 나타냅니다.


1

여기 @ars 허용 대답과 사용 예제 BeautifulSoup4, requests그리고 wget다운로드를 처리하는 모듈.

import requests
import wget
import os

from bs4 import BeautifulSoup, SoupStrainer

url = 'https://archive.ics.uci.edu/ml/machine-learning-databases/eeg-mld/eeg_full/'
file_type = '.tar.gz'

response = requests.get(url)

for link in BeautifulSoup(response.content, 'html.parser', parse_only=SoupStrainer('a')):
    if link.has_attr('href'):
        if file_type in link['href']:
            full_path = url + link['href']
            wget.download(full_path)

1

다음 수정 후 (@ Blairg23 working)의 답변을 찾았습니다 (정확하게 작동하지 않은 시나리오 포함).

for link in BeautifulSoup(response.content, 'html.parser', parse_only=SoupStrainer('a')):
    if link.has_attr('href'):
        if file_type in link['href']:
            full_path =urlparse.urljoin(url , link['href']) #module urlparse need to be imported
            wget.download(full_path)

파이썬 3의 경우 :

urllib.parse.urljoin 대신 전체 URL을 얻으려면 사용해야합니다.


1

BeatifulSoup의 자체 파서는 느려질 수 있습니다. URL에서 직접 구문 분석 할 수있는 lxml 을 사용하는 것이 더 가능할 수 있습니다 (아래에 언급 된 일부 제한 사항이 있음).

import lxml.html

doc = lxml.html.parse(url)

links = doc.xpath('//a[@href]')

for link in links:
    print link.attrib['href']

위의 코드는 링크를 그대로 반환하며 대부분의 경우 링크는 상대 링크이거나 사이트 루트에서 절대 링크입니다. 내 유스 케이스는 특정 유형의 링크 만 추출하는 것이기 때문에 링크를 전체 URL로 변환하고 선택적으로와 같은 glob 패턴을 허용하는 버전입니다 *.mp3. 그러나 상대 경로에서 단일 및 이중 점을 처리하지는 않지만 지금까지는 필요하지 않았습니다. 당신이 포함 된 구문 분석 URL 조각에 필요한 경우 ../또는 ./다음 urlparse.urljoin은 편리 할 수도 있습니다.

참고 : 직접 lxml URL 구문 분석은로드를 처리 https하지 않으며 리디렉션을 수행하지 않으므로 아래 버전에서는 urllib2+를 사용 lxml합니다.

#!/usr/bin/env python
import sys
import urllib2
import urlparse
import lxml.html
import fnmatch

try:
    import urltools as urltools
except ImportError:
    sys.stderr.write('To normalize URLs run: `pip install urltools --user`')
    urltools = None


def get_host(url):
    p = urlparse.urlparse(url)
    return "{}://{}".format(p.scheme, p.netloc)


if __name__ == '__main__':
    url = sys.argv[1]
    host = get_host(url)
    glob_patt = len(sys.argv) > 2 and sys.argv[2] or '*'

    doc = lxml.html.parse(urllib2.urlopen(url))
    links = doc.xpath('//a[@href]')

    for link in links:
        href = link.attrib['href']

        if fnmatch.fnmatch(href, glob_patt):

            if not href.startswith(('http://', 'https://' 'ftp://')):

                if href.startswith('/'):
                    href = host + href
                else:
                    parent_url = url.rsplit('/', 1)[0]
                    href = urlparse.urljoin(parent_url, href)

                    if urltools:
                        href = urltools.normalize(href)

            print href

사용법은 다음과 같습니다.

getlinks.py http://stackoverflow.com/a/37758066/191246
getlinks.py http://stackoverflow.com/a/37758066/191246 "*users*"
getlinks.py http://fakedomain.mu/somepage.html "*.mp3"

lxml유효한 입력 만 처리 할 수 ​​있으며 어떻게 교체 할 수 BeautifulSoup있습니까?
Alexis

@alexis : lxml.html보다 약간 관대 하다고 생각 합니다 lxml.etree. 귀하의 의견이 잘 형성되지 않는 경우에 당신은 명시 적으로 BeautifulSoup로 파서를 설정할 수 있습니다 lxml.de/elementsoup.html를 . BeatifulSoup을 사용한다면 BS3가 더 나은 선택입니다.
ccpizza 2016 년

0
import urllib2
from bs4 import BeautifulSoup
a=urllib2.urlopen('http://dir.yahoo.com')
code=a.read()
soup=BeautifulSoup(code)
links=soup.findAll("a")
#To get href part alone
print links[0].attrs['href']

0

외부 및 내부 링크와 함께 많은 중복 링크가있을 수 있습니다. 둘을 구별하고 세트를 사용하여 고유 링크를 얻으려면 다음을 수행하십시오.

# Python 3.
import urllib    
from bs4 import BeautifulSoup

url = "http://www.espncricinfo.com/"
resp = urllib.request.urlopen(url)
# Get server encoding per recommendation of Martijn Pieters.
soup = BeautifulSoup(resp, from_encoding=resp.info().get_param('charset'))  
external_links = set()
internal_links = set()
for line in soup.find_all('a'):
    link = line.get('href')
    if not link:
        continue
    if link.startswith('http'):
        external_links.add(link)
    else:
        internal_links.add(link)

# Depending on usage, full internal links may be preferred.
full_internal_links = {
    urllib.parse.urljoin(url, internal_link) 
    for internal_link in internal_links
}

# Print all unique external and full internal links.
for link in external_links.union(full_internal_links):
    print(link)
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.