如何使用Python的“requests”模块进行简单快速请求?

11

我是Python的初学者,仅尝试使用requestsBeautifulSoup模块来抓取网页。 我正在对这个网站进行请求。

这是我的简单代码:

import requests, time, re, json
from bs4 import BeautifulSoup as BS

url = "https://www.jobstreet.co.id/en/job-search/job-vacancy.php?ojs=6"

def list_jobs():
    try:
        with requests.session() as s:
            st = time.time()
            s.headers = {'User-Agent':'Mozilla/5.0 (Windows NT 6.3; WOW64; rv:57.0) Gecko/20100101 Firefox/57.0'}
            req = s.get(url)
            soup = BS(req.text,'html.parser')
            attr = soup.findAll('div',class_='position-title header-text')
            pttr = r".?(.*)Rank=\d+"
            lists = {"status":200,"result":[]}
            for a in attr:
                sr = re.search(pttr, a.find("a")["href"])
                if sr:
                    title = a.find('a')['title'].replace("Lihat detil lowongan -","").replace("\r","").replace("\n","")
                    url = a.find('a')['href']
                    lists["result"].append({
                        "title":title,
                        "url":url,
                        "detail":detail_jobs(url)
                    })
            print(json.dumps(lists, indent=4))
            end = time.time() - st
            print(f"\n{end} second")
    except:
        pass

def detail_jobs(find_url):
    try:
        with requests.session() as s:
            s.headers = {'User-Agent':'Mozilla/5.0 (Windows NT 6.3; WOW64; rv:57.0) Gecko/20100101 Firefox/57.0'}
            req = s.get(find_url)
            soup = BS(req.text,'html.parser')
            position = soup.find('h1',class_='job-position').text
            name = soup.find('div',class_='company_name').text.strip("\t")
            try:
                addrs = soup.find('div',class_='map-col-wraper').find('p',{'id':'address'}).text
            except Exception:
                addrs = "Unknown"
            try:
                loct = soup.find('span',{'id':'single_work_location'}).text
            except Exception:
                loct = soup.find('span',{'id':'multiple_work_location_list'}).find('span',{'class':'show'}).text        
            dests = soup.findAll('div',attrs={'id':'job_description'})
            for select in dests:
                txt = select.text if not select.text.startswith("\n") or not select.text.endswith("\n") else select.text.replace("\n","")
                result = {
                    "name":name,
                    "location":loct,
                    "position":position,
                    "description":txt,
                    "address":addrs
                }
                return result
    except:
        pass

它们都能很好地工作,但需要很长时间才能显示结果,时间总是超过13/17秒。

我不知道如何提高请求速度。

我尝试在Stack和Google上搜索,他们说使用asyncio,但这种方式对我来说太难。

如果有人有简单的技巧可以帮助我快速提高速度,我将不胜感激......

对我的英语不好,抱歉。

4个回答

20

通过像网页抓取这样的项目学习Python真是太棒了。也正是通过这种方式我才接触到了Python。 话虽如此,为了增加您的抓取速度,您可以做三件事:

  1. 将html解析器更改为更快的解析器。 'html.parser' 是最慢的解析器之一。尝试更改为'lxml'或'html5lib'。(请阅读https://www.crummy.com/software/BeautifulSoup/bs4/doc/

enter image description here

  1. 放弃循环和正则表达式,因为它们会减慢脚本的速度。只需使用BeautifulSoup工具、文本和strip,并找到正确的标记。(见下面我的脚本)

  2. 由于网络爬虫的瓶颈通常是IO,等待从网页获取数据,使用async或multithread将提高速度。在下面的脚本中,我使用了多线程。目的是同时从多个页面获取数据。

因此,如果我们知道最大页面数量,我们可以将请求分成不同的范围,并批量获取它们 :)

代码示例:

from collections import defaultdict
from concurrent.futures import ThreadPoolExecutor
from datetime import datetime

import requests
from bs4 import BeautifulSoup as bs

data = defaultdict(list)

headers = {'User-Agent':'Mozilla/5.0 (Windows NT 6.3; WOW64; rv:57.0) Gecko/20100101 Firefox/57.0'}

def get_data(data, headers, page=1):

    # Get start time
    start_time = datetime.now()
    url = f'https://www.jobstreet.co.id/en/job-search/job-vacancy/{page}/?src=20&srcr=2000&ojs=6'
    r = requests.get(url, headers=headers)

    # If the requests is fine, proceed
    if r.ok:
        jobs = bs(r.content,'lxml').find('div',{'id':'job_listing_panel'})
        data['title'].extend([i.text.strip() for i in jobs.find_all('div',{'class':'position-title header-text'})])
        data['company'].extend([i.text.strip() for i in jobs.find_all('h3',{'class':'company-name'})])
        data['location'].extend([i['title'] for i in jobs.find_all('li',{'class':'job-location'})] )
        data['desc'].extend([i.text.strip() for i in jobs.find_all('ul',{'class':'list-unstyled hidden-xs '})])
    else:
        print('connection issues')
    print(f'Page: {page} | Time taken {datetime.now()-start_time}')
    return data
    

def multi_get_data(data,headers,start_page=1,end_page=20,workers=20):
    start_time = datetime.now()
    # Execute our get_data in multiple threads each having a different page number
    with ThreadPoolExecutor(max_workers=workers) as executor:
        [executor.submit(get_data, data=data,headers=headers,page=i) for i in range(start_page,end_page+1)]
    
    print(f'Page {start_page}-{end_page} | Time take {datetime.now() -     start_time}')
    return data


# Test page 10-15
k = multi_get_data(data,headers,start_page=10,end_page=15)

结果: enter image description here

解释 multi_get_data 函数:

此函数将使用不同线程调用 get_data 函数并传递所需参数。目前,每个线程获得不同的页面编号进行调用。最大工人数设置为20,即20个线程。您可以相应地增加或减少。

我们已创建变量 data ,这是一个默认字典,其中包含列表。所有线程都将填充此数据。然后,可以将此变量转换为 json 或 Pandas DataFrame :)

正如您所看到的,我们有5个请求,每个请求不到2秒钟,但总时间仍然不到2秒钟;)

享受网络爬虫吧。

更新_: 22/12/2019

我们还可以通过使用会话和单个标题更新来提高速度。因此,我们不必在每次调用时启动会话。


















from requests import Session

s = Session()
headers = {'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) '\
                         'AppleWebKit/537.36 (KHTML, like Gecko) '\
                         'Chrome/75.0.3770.80 Safari/537.36'}
# Add headers
s.headers.update(headers)

# we can use s as we do requests
# s.get(...)
...

1
优秀的Prayson! - Mikko Ohtamaa
1
太棒了,我会尝试并改进它。我想知道如何使用更快的速度进行简单的do请求,也许我可以使用你的技巧;) - alnyz
玩弄并调整它以适应您的需求。它在您的示例中收集了相同的数据。我删除了数据的打印,因为这会减慢进程。您可以随后打印您的数据,例如导入pandas作为pd,并将k添加为df = pd.DataFrame(k)。现在,您可以打印print(df.head()),并将数据加载到csv中,如df.to_csv('data.csv',index = False,sep =';') - Prayson W. Daniel

2

瓶颈是服务器对简单请求响应缓慢。

尝试并行发出请求。

您也可以使用线程而不是异步io。这里有一个先前的问题解释如何在Python中并行执行任务:

在python中并行执行任务

请注意,即使智能配置的服务器仍会在未经许可的情况下减慢您的请求或禁止您进行爬取。


2

我建议你使用良好的架构编写代码,并将其分成函数,并尽量减少代码量。以下是使用 request 的示例:

from requests import get
from requests.exceptions import RequestException
from contextlib import closing
from bs4 import BeautifulSoup

def simple_get(url):
    """
    Attempts to get the content at `url` by making an HTTP GET request.
    If the content-type of response is some kind of HTML/XML, return the
    text content, otherwise return None.
    """
    try:
        with closing(get(url, stream=True)) as resp:
            if is_good_response(resp):
                return resp.content
            else:
                return None

    except RequestException as e:
        log_error('Error during requests to {0} : {1}'.format(url, str(e)))
        return None


def is_good_response(resp):
    """
    Returns True if the response seems to be HTML, False otherwise.
    """
    content_type = resp.headers['Content-Type'].lower()
    return (resp.status_code == 200 
            and content_type is not None 
            and content_type.find('html') > -1)


def log_error(e):
    """
    It is always a good idea to log errors. 
    This function just prints them, but you can
    make it do anything.
    """
    print(e)

在耗时的代码段上进行调试,找出问题并在此讨论。这样可以帮助您解决问题。


0

尝试使用scrapy,它将为您处理网站通信(请求/响应)。

如果您发出太多请求,您将被阻止,因为他们正在使用Cloudflare产品。


能够添加一个简单的使用Scrapy的例子会很不错 ;) - Prayson W. Daniel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接