343

Có cách nào để lấy cả tiêu đề và nội dung cho yêu cầu cURL bằng PHP không? Tôi thấy rằng tùy chọn này:

curl_setopt($ch, CURLOPT_HEADER, true);

sẽ trả về phần nội dung cộng với tiêu đề , nhưng sau đó tôi cần phải phân tích cú pháp nó để lấy phần nội dung. Có cách nào để có được cả hai theo cách hữu dụng hơn (và an toàn) không?

Lưu ý rằng đối với "yêu cầu duy nhất", tôi có nghĩa là tránh đưa ra yêu cầu HEAD trước GET / POST.

|
504

Một giải pháp cho vấn đề này đã được đăng trong phần nhận xét tài liệu PHP: http://www.php.net/manual/en/ Chức năng.curl-exec.php#80442

Ví dụ về mã:

$ch = curl_init();
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_HEADER, 1);
// ...

$response = curl_exec($ch);

// Then, after your curl_exec call:
$header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
$header = substr($response, 0, $header_size);
$body = substr($response, $header_size);

Cảnh báo: Như đã lưu ý trong các nhận xét bên dưới, điều này có thể không đáng tin cậy khi được sử dụng với máy chủ proxy hoặc khi xử lý một số loại chuyển hướng. Câu trả lời của @ Geoffrey có thể giải quyết những vấn đề này một cách đáng tin cậy hơn.

|
  • 1
    Bạn cũng có thể làm được list($header, $body) = explode("\r\n\r\n", $response, 2), nhưng quá trình này có thể lâu hơn một chút, tùy thuộc vào kích thước yêu cầu của bạn. –  21:33:54 07/02/2012
  • 1
    đây là giải pháp xấu bởi vì nếu bạn sử dụng máy chủ proxy và proxy server của bạn (cáy ví dụ) thêm tiêu đề riêng để đáp ứng - tiêu đề này đã phá vỡ tất cả các offsets và bạn nên sử dụng list($header, $body) = explode("\r\n\r\n", $response, 2)như chỉ biến làm việc –  02:17:37 25/02/2013
  • 1
    @msangel Giải pháp của bạn không hoạt động khi có nhiều tiêu đề trong phản hồi, chẳng hạn như khi máy chủ thực hiện chuyển hướng 302. Bất kỳ đề xuất? –  00:32:03 08/04/2014
  • 1
    @Nate, vâng, tôi biết điều này. AFAIK, nhưng chỉ có một tiêu đề bổ sung có thể có - với mã 100(Tiếp tục). Đối với tiêu đề này, bạn có thể thực hiện với tùy chọn yêu cầu xác định chính xác curl_setopt($ch, CURLOPT_HTTPHEADER, array('Expect:')); :, vô hiệu hóa việc gửi phản hồi tiêu đề này. Về phần 302, điều này không nên xảy ra, vì tiêu đề 302 được chuyển hướng, nó không mong đợi nội dung, tuy nhiên tôi biết, đôi khi máy chủ gửi một số nội dung kèm theo 302phản hồi, nhưng nó sẽ bị trình duyệt bỏ qua, cho đến nay, tại sao curl nên xử lý điều này? ) –  14:37:50 08/04/2014
  • 1
    CURLOPT_VERBOSEnhằm mục đích xuất ra thông tin quy trình STDERR(có thể làm phiền trong CLI) và đối với vấn đề đã thảo luận là vô ích. –  15:46:07 08/04/2015
272

Nhiều giải pháp khác được cung cấp chủ đề này không thực hiện điều này một cách chính xác.

  • Việc phân tách trên \r\n\r\nkhông đáng tin cậy khi CURLOPT_FOLLOWLOCATIONbật hoặc khi máy chủ phản hồi bằng mã 100.
  • Không phải tất cả các máy chủ đều tuân thủ các tiêu chuẩn và chỉ truyền \ncho các đường truyền mới.
  • Việc phát hiện kích thước của tiêu đề thông qua CURLINFO_HEADER_SIZEcũng không phải lúc nào cũng đáng tin cậy, đặc biệt khi proxy được sử dụng hoặc trong một số tình huống chuyển hướng giống nhau.

Phương pháp đúng nhất là sử dụng CURLOPT_HEADERFUNCTION.

Đây là một phương pháp rất rõ ràng để thực hiện việc này bằng cách sử dụng các bao đóng PHP. Nó cũng chuyển đổi tất cả các tiêu đề thành chữ thường để xử lý nhất quán trên các máy chủ và các phiên bản HTTP.

Phiên bản này sẽ giữ lại các tiêu đề trùng lặp

Điều này tuân thủ RFC822 và RFC2616, vui lòng không đề xuất chỉnh sửa để sử dụng các mb_hàm chuỗi, nó không chính xác!

$ch = curl_init();
$headers = [];
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);

// this function is called by curl for each header received
curl_setopt($ch, CURLOPT_HEADERFUNCTION,
  function($curl, $header) use (&$headers)
  {
    $len = strlen($header);
    $header = explode(':', $header, 2);
    if (count($header) < 2) // ignore invalid headers
      return $len;

    $headers[strtolower(trim($header[0]))][] = trim($header[1]);

    return $len;
  }
);

$data = curl_exec($ch);
print_r($headers);
|
  • 1
    IMO đây là câu trả lời hay nhất trong chủ đề này và khắc phục sự cố với chuyển hướng xảy ra với các câu trả lời khác. Tốt nhất hãy đọc tài liệu về CURLOPT_HEADERFUNCTION để hiểu cách hoạt động và các lỗi tiềm năng. Tôi cũng đã thực hiện một số cải tiến cho câu trả lời để giúp những người khác. –  07:06:48 09/06/2017
  • 1
    @thealexbaron Có nó là như PHP 5.4, xem: php.net/manual/en/migration54.new-features.php –  00:39:02 18/07/2017
  • 1
    Câu trả lời này được đánh giá thấp đối với cách tiếp cận gọn gàng và tuân thủ RFC. Câu trả lời này sẽ được tạo thành câu trả lời cố định và chuyển lên đầu. Tôi chỉ ước có một cách tiếp cận nhanh hơn để có được giá trị của một tiêu đề mong muốn thay vì phân tích cú pháp tất cả các tiêu đề trước. –  09:01:46 17/05/2018
  • 1
    @ Mahesh.D không, $data = curl_exec($ch);trả về nội dung khi CURLOPT_RETURNTRANSFERđược đặt theo ví dụ được cung cấp. –  11:26:41 26/06/2019
  • 1
    Câu trả lời chất lượng, cảm ơn! –  15:52:50 16/02/2021
125

Curl có một tùy chọn tích hợp cho việc này, được gọi là CURLOPT_HEADERFUNCTION. Giá trị của tùy chọn này phải là tên của một hàm gọi lại. Curl sẽ truyền tiêu đề (và chỉ tiêu đề!) Cho hàm gọi lại này, từng dòng một (vì vậy hàm sẽ được gọi cho mỗi dòng tiêu đề, bắt đầu từ đầu phần tiêu đề). Sau đó, hàm gọi lại của bạn có thể làm bất cứ điều gì với nó (và phải trả về số byte của dòng đã cho). Đây là mã làm việc đã được thử nghiệm:

function HandleHeaderLine( $curl, $header_line ) {
    echo "<br>YEAH: ".$header_line; // or do whatever
    return strlen($header_line);
}


$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "http://www.google.com");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_HEADERFUNCTION, "HandleHeaderLine");
$body = curl_exec($ch); 

Ở trên hoạt động với mọi thứ, các giao thức và proxy khác nhau, và bạn không cần phải lo lắng về kích thước tiêu đề hoặc đặt nhiều tùy chọn cuộn tròn khác nhau.

Tái bút: Để xử lý các dòng tiêu đề bằng một phương thức đối tượng, hãy làm như sau:

curl_setopt($ch, CURLOPT_HEADERFUNCTION, array(&$object, 'methodName'))
|
  • 1
    Như một lưu ý, hàm gọi lại được gọi cho mỗi tiêu đề và có vẻ như chúng không được cắt bớt. Bạn có thể sử dụng một biến toàn cục để chứa tất cả các tiêu đề hoặc bạn có thể sử dụng một hàm ẩn danh cho lệnh gọi lại và sử dụng một biến cục bộ (cục bộ cho phạm vi mẹ, không phải hàm ẩn danh). –  23:55:45 25/04/2015
  • 1
    @MV Cảm ơn, vâng, theo "từng dòng", ý tôi là "từng tiêu đề". Tôi đã chỉnh sửa câu trả lời của mình cho rõ ràng. Để lấy toàn bộ phần tiêu đề (hay còn gọi là tất cả các tiêu đề), bạn cũng có thể sử dụng một phương thức đối tượng cho lệnh gọi lại để một thuộc tính đối tượng có thể chứa tất cả chúng. –  12:06:20 26/04/2015
  • 1
    Đây là câu trả lời tốt nhất IMO. Nó không gây ra sự cố với nhiều "\ r \ n \ r \ n" khi sử dụng CURLOPT_FOLLOWLOCATION và tôi đoán nó sẽ không bị ảnh hưởng bởi các tiêu đề bổ sung từ proxy. –  11:43:43 02/07/2015
  • 1
    Hoạt động rất tốt đối với tôi, cũng xem stackoverflow.com/questions/6482068/… trong trường hợp có vấn đề –  15:12:49 04/04/2016
  • 1
    Có, đây là cách tiếp cận tốt nhất, tuy nhiên, câu trả lời của @ Geoffrey làm cho điều này trở nên gọn gàng hơn bằng cách sử dụng một hàm ẩn danh mà không cần các biến toàn cục và tương tự. –  06:50:52 09/06/2017
39

đây có phải là những gì bạn đang tìm kiếm?

curl_setopt($ch, CURLOPT_HTTPHEADER, array('Expect:'));
$response = curl_exec($ch); 
list($header, $body) = explode("\r\n\r\n", $response, 2);
|
9

Chỉ cần đặt các tùy chọn:

  • CURLOPT_HEADER, 0

  • CURLOPT_RETURNTRANSFER, 1

và sử dụng curl_getinfo với CURLINFO_HTTP_CODE (hoặc không có tham số tùy chọn và bạn sẽ có một mảng liên kết với tất cả thông tin bạn muốn)

Xem thêm tại: http://php.net/manual/fr/ Chức năng.curl-getinfo.php

|
  • 1
    Điều này dường như không trả lại tiêu đề phản hồi cho bạn. Hoặc ít nhất là không có cách nào để lấy chúng bằng cách sử dụng curl_getinfo(). –  22:39:19 08/06/2017
8

Nếu bạn đặc biệt muốn Content-Type, có một tùy chọn cURL đặc biệt để truy xuất nó:

$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$response = curl_exec($ch);
$content_type = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
|
  • 1
    OP đã hỏi liệu có cách nào để lấy các tiêu đề, không phải một tiêu đề cụ thể, điều này không trả lời câu hỏi của OP. –  02:38:26 06/06/2019
2
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_VERBOSE, 1);
curl_setopt($ch, CURLOPT_HEADER, 1);

$parts = explode("\r\n\r\nHTTP/", $response);
$parts = (count($parts) > 1 ? 'HTTP/' : '').array_pop($parts);
list($headers, $body) = explode("\r\n\r\n", $parts, 2);

Hoạt động với HTTP/1.1 100 Continuetrước các tiêu đề khác.

Nếu bạn cần làm việc với các máy chủ lỗi chỉ gửi LF thay vì CRLF khi ngắt dòng, bạn có thể sử dụng preg_splitnhư sau:

curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_VERBOSE, 1);
curl_setopt($ch, CURLOPT_HEADER, 1);

$parts = preg_split("@\r?\n\r?\nHTTP/@u", $response);
$parts = (count($parts) > 1 ? 'HTTP/' : '').array_pop($parts);
list($headers, $body) = preg_split("@\r?\n\r?\n@u", $parts, 2);
|
  • 1
    Không nên $parts = explode("\r\n\r\nHTTP/", $response);có tham số thứ 3 cho bùng nổ là 2? –  19:16:40 21/08/2015
  • 1
    @ user4271704 Không. Nó cho phép tìm thông báo HTTP cuối cùng. HTTP/1.1 100 Continuecó thể xuất hiện nhiều lần. –  09:25:54 22/08/2015
  • 1
    Nhưng anh ấy nói điều gì đó khác: stackoverflow.com/questions/9183178/… câu nào trong số bạn đúng? –  09:20:46 24/08/2015
  • 1
    HTTP/1.1 100 Continuecó thể xuất hiện nhiều lần. Ông xem trường hợp nếu nó chỉ xuất hiện một lần, nhưng nó sai trong trường hợp phổ biến. Ví dụ đối với HTTP/1.1 100 Continue\r\n\r\nHTTP/1.1 100 Continue\r\n\r\nHTTP/1.1 200 OK...\r\n\r\n...mã của mình không hoạt động đúng –  09:35:39 24/08/2015
  • 1
    Việc phân tách trên \ r \ n là không đáng tin cậy, một số máy chủ không tuân theo các thông số kỹ thuật HTTP và sẽ chỉ gửi một \ n. Tiêu chuẩn RFC tuyên bố rằng các ứng dụng nên bỏ qua \ r và chia nhỏ trên \ n để có độ tin cậy cao nhất. –  07:24:27 09/06/2017
1

Đây là đóng góp của tôi cho cuộc tranh luận ... Điều này trả về một mảng duy nhất với dữ liệu được phân tách và các tiêu đề được liệt kê. Điều này hoạt động trên cơ sở CURL sẽ trả về dữ liệu chunk tiêu đề [dòng trống]

curl_setopt($ch, CURLOPT_HEADER, 1); // we need this to get headers back
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_VERBOSE, true);

// $output contains the output string
$output = curl_exec($ch);

$lines = explode("\n",$output);

$out = array();
$headers = true;

foreach ($lines as $l){
    $l = trim($l);

    if ($headers && !empty($l)){
        if (strpos($l,'HTTP') !== false){
            $p = explode(' ',$l);
            $out['Headers']['Status'] = trim($p[1]);
        } else {
            $p = explode(':',$l);
            $out['Headers'][$p[0]] = trim($p[1]);
        }
    } elseif (!empty($l)) {
        $out['Data'] = $l;
    }

    if (empty($l)){
        $headers = false;
    }
}
|
1

Cách của tôi là

$response = curl_exec($ch);
$x = explode("\r\n\r\n", $v, 3);
$header=http_parse_headers($x[0]);
if ($header=['Response Code']==100){ //use the other "header"
    $header=http_parse_headers($x[1]);
    $body=$x[2];
}else{
    $body=$x[1];
}

Nếu cần, hãy áp dụng vòng lặp for và loại bỏ giới hạn phát nổ.

|
0

Một cách tốt hơn là sử dụng phản hồi CURL dài dòng có thể được chuyển đến một luồng tạm thời. Sau đó, bạn có thể tìm kiếm phản hồi cho tên tiêu đề. Điều này có thể sử dụng một vài chỉnh sửa nhưng nó phù hợp với tôi:

class genericCURL {
    /**
     * NB this is designed for getting data, or for posting JSON data
     */
    public function request($url, $method = 'GET', $data = array()) {
        $ch = curl_init();
        
        if($method == 'POST') {
            
            curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");
            curl_setopt($ch, CURLOPT_POSTFIELDS, $string = json_encode($data));
            
        }

        
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_VERBOSE, true);
        
        //open a temporary stream to output the curl log, which would normally got to STDERR
        $err = fopen("php://temp", "w+");
        curl_setopt($ch, CURLOPT_STDERR, $err);
        

        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        $server_output = curl_exec ($ch);
        
        //rewind the temp stream and put it into a string   
        rewind($err);
        $this->curl_log = stream_get_contents($err);
        
        curl_close($ch);
        fclose($err);

    
        return $server_output;
        
    }
    
    /**
     * use the curl log to get a header value
     */
    public function getReturnHeaderValue($header) {
        $log = explode("\n", str_replace("\r\n", "\n", $this->curl_log));
        foreach($log as $line) {
            //is the requested header there
            if(stripos($line, '< ' . $header . ':') !== false) {
                $value = trim(substr($line, strlen($header) + 3));
                return $value;
            }
        }
        //still here implies not found so return false
        return false;
        
    }
}
|
0

Hãy thử điều này nếu bạn đang sử dụng GET:

$curl = curl_init($url);

curl_setopt_array($curl, array(
    CURLOPT_URL => $url,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_ENCODING => "",
    CURLOPT_MAXREDIRS => 10,
    CURLOPT_TIMEOUT => 30,
    CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
    CURLOPT_CUSTOMREQUEST => "GET",
    CURLOPT_HTTPHEADER => array(
        "Cache-Control: no-cache"
    ),
));

$response = curl_exec($curl);
curl_close($curl);
|
0

Vấn đề với nhiều câu trả lời ở đây là nó "\r\n\r\n"có thể xuất hiện một cách hợp pháp trong phần nội dung của html, vì vậy bạn không thể chắc chắn rằng mình đang tách các tiêu đề một cách chính xác.

Có vẻ như cách duy nhất để lưu trữ các tiêu đề riêng biệt với một lần gọi tới curl_execlà sử dụng một lệnh gọi lại như được đề xuất ở trên trong https://stackoverflow.com/a/25118032/3326494

Và sau đó để (đáng tin cậy) chỉ lấy phần nội dung của yêu cầu, bạn sẽ cần chuyển giá trị của Content-Lengthtiêu đề thành substr()giá trị bắt đầu âm.

|
  • 1
    Nó có thể xuất hiện một cách hợp pháp, nhưng câu trả lời của bạn không chính xác. Nội dung-Độ dài không nhất thiết phải có trong phản hồi HTTP. Phương pháp chính xác để phân tích cú pháp thủ công các tiêu đề là tìm kiếm phiên bản đầu tiên của \ r \ n (hoặc \ n \ n). Điều này có thể được thực hiện đơn giản bằng cách hạn chế phát nổ trở lại chỉ có hai yếu tố, ví dụ: list($head, $body) = explode("\r\n\r\n", $response, 2);Tuy nhiên CURL đã thực hiện điều này cho bạn nếu bạn sử dụngcurl_setopt($ch, CURLOPT_HEADERFUNCTION, $myFunction); –  07:26:28 09/06/2017
-1

Chỉ trong trường hợp bạn không thể / không sử dụng CURLOPT_HEADERFUNCTIONhoặc các giải pháp khác;

$nextCheck = function($body) {
    return ($body && strpos($body, 'HTTP/') === 0);
};

[$headers, $body] = explode("\r\n\r\n", $result, 2);
if ($nextCheck($body)) {
    do {
        [$headers, $body] = explode("\r\n\r\n", $body, 2);
    } while ($nextCheck($body));
}
|
-2

Trả lại các tiêu đề phản hồi với một tham số tham chiếu:

<?php
$data=array('device_token'=>'5641c5b10751c49c07ceb4',
            'content'=>'测试测试test'
           );
$rtn=curl_to_host('POST', 'http://test.com/send_by_device_token', array(), $data, $resp_headers);
echo $rtn;
var_export($resp_headers);

function curl_to_host($method, $url, $headers, $data, &$resp_headers)
         {$ch=curl_init($url);
          curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $GLOBALS['POST_TO_HOST.LINE_TIMEOUT']?$GLOBALS['POST_TO_HOST.LINE_TIMEOUT']:5);
          curl_setopt($ch, CURLOPT_TIMEOUT, $GLOBALS['POST_TO_HOST.TOTAL_TIMEOUT']?$GLOBALS['POST_TO_HOST.TOTAL_TIMEOUT']:20);
          curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
          curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false);
          curl_setopt($ch, CURLOPT_HEADER, 1);

          if ($method=='POST')
             {curl_setopt($ch, CURLOPT_POST, true);
              curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data));
             }
          foreach ($headers as $k=>$v)
                  {$headers[$k]=str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', $k)))).': '.$v;
                  }
          curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
          $rtn=curl_exec($ch);
          curl_close($ch);

          $rtn=explode("\r\n\r\nHTTP/", $rtn, 2);    //to deal with "HTTP/1.1 100 Continue\r\n\r\nHTTP/1.1 200 OK...\r\n\r\n..." header
          $rtn=(count($rtn)>1 ? 'HTTP/' : '').array_pop($rtn);
          list($str_resp_headers, $rtn)=explode("\r\n\r\n", $rtn, 2);

          $str_resp_headers=explode("\r\n", $str_resp_headers);
          array_shift($str_resp_headers);    //get rid of "HTTP/1.1 200 OK"
          $resp_headers=array();
          foreach ($str_resp_headers as $k=>$v)
                  {$v=explode(': ', $v, 2);
                   $resp_headers[$v[0]]=$v[1];
                  }

          return $rtn;
         }
?>
|
  • 1
    Bạn có chắc $rtn=explode("\r\n\r\nHTTP/", $rtn, 2);là chính xác? Không nên loại bỏ thông số thứ 3 của sự bùng nổ? –  18:11:19 21/08/2015
  • 1
    @ user4271704, các param thứ 3 là để đối phó với "HTTP / 1.1 100 Tiếp tục \ r \ n \ r \ nhttp / 1.1 200 OK ... \ r \ n \ r \ n ..." tiêu đề –  03:00:13 24/08/2015
  • 1
    Nhưng anh ấy đã nói điều gì đó khác: stackoverflow.com/questions/9183178/… câu nào trong số bạn đúng? –  09:19:30 24/08/2015
  • 1
    @ user4271704 liên kết bạn đang đề cập đến cũng sử dụng: explode("\r\n\r\n", $parts, 2); vì vậy cả hai đều đúng. –  01:25:31 17/09/2019
-5

Nếu bạn không thực sự cần thiết phải sử dụng thuốc uốn tóc;

$body = file_get_contents('http://example.com');
var_export($http_response_header);
var_export($body);

Đầu ra nào

array (
  0 => 'HTTP/1.0 200 OK',
  1 => 'Accept-Ranges: bytes',
  2 => 'Cache-Control: max-age=604800',
  3 => 'Content-Type: text/html',
  4 => 'Date: Tue, 24 Feb 2015 20:37:13 GMT',
  5 => 'Etag: "359670651"',
  6 => 'Expires: Tue, 03 Mar 2015 20:37:13 GMT',
  7 => 'Last-Modified: Fri, 09 Aug 2013 23:54:35 GMT',
  8 => 'Server: ECS (cpm/F9D5)',
  9 => 'X-Cache: HIT',
  10 => 'x-ec-custom-error: 1',
  11 => 'Content-Length: 1270',
  12 => 'Connection: close',
)'<!doctype html>
<html>
<head>
    <title>Example Domain</title>...

Xem http://php.net/manual/en/reserved.variables.httpresponseheader.php

|
  • 1
    uhm, bạn cũng không thực sự cần PHP, nhưng điều đó chỉ xảy ra với câu hỏi về ... –  20:55:00 24/02/2015

Câu trả lời của bạn (> 20 ký tự)

Bằng cách click "Đăng trả lời", bạn đồng ý với Điều khoản dịch vụ, Chính sách bảo mật and Chính sách cookie của chúng tôi.

Không tìm thấy câu trả lời bạn tìm kiếm? Duyệt qua các câu hỏi được gắn thẻ hoặc hỏi câu hỏi của bạn.