Сайт Scrapy и ASPX — почему он зацикливается только на первой странице?

#python #scrapy

#python #шершавый

Вопрос:

Я пытаюсь извлечь данные с этого сайта: https://inform.alabama.gov/employeesearch.aspx . Когда я запускаю этот код в настоящее время, он просто повторно извлекает данные из «Страницы $ 1». На самом деле цикл, похоже, не выполняет итерацию запроса. Это мой первый скрипт как на Python, так и на Scrapy, поэтому я уверен, что здесь мне не хватает чего-то фундаментального…

Каков правильный способ получить ответ от одного запроса и передать данные, извлеченные из него, следующему?

На этом сайте также нет кнопки «Далее», поэтому я использовал переменную num_pages для установки максимального значения. Открыт для предложений о том, как сделать это динамичным.

 from scrapy import FormRequest, Spider
from scrapy.spidermiddlewares.httperror import HttpError
from twisted.internet.error import DNSLookupError
from twisted.internet.error import TimeoutError, TCPTimedOutError


class EmployeesSpider(Spider):
    name = 'employees'
    start_urls = ['https://inform.alabama.gov/employeesearch.aspx']
    num_pages = 5  # 661

    def parse(self, response):
        for i in range(1, self.num_pages):
            yield FormRequest(
                url='https://inform.alabama.gov/employeesearch.aspx',
                method="POST",
                dont_filter=True,
                headers={
                    "authority": "inform.alabama.gov",
                    "cache-control": "max-age=0",
                    "upgrade-insecure-requests": "1",
                    "origin": "https://inform.alabama.gov",
                    "content-type": "application/x-www-form-urlencoded",
                    "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36",
                    "accept": "text/html,application/xhtml xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
                    "sec-fetch-site": "same-origin",
                    "sec-fetch-mode": "navigate",
                    "sec-fetch-user": "?1",
                    "sec-fetch-dest": "document",
                    "referer": "https://inform.alabama.gov/employeesearch.aspx",
                    "accept-language": "en-US,en;q=0.9"
                },
                formdata=self.get_formdata(response, i),
                callback=self.parse_results,
                errback=self.failure)

    def get_formdata(self, response, page_num):
        eventargument = 'Page


Ответ №1:

Вам необходимо сначала отправить запрос формы поиска ( __EVENTTARGET=ctl00$ContentPlaceHolder1$btn_Search ), а затем выполнить итерацию страниц результатов ( __EVENTTARGET=ctl00$ContentPlaceHolder1$GridView1 ).:

 def parse(self, response):
    # for i in range(1, 2):
    formdata = self.get_formdata(response, 0)
    formdata['__EVENTTARGET'] = 'ctl00$ContentPlaceHolder1$btn_Search'
    formdata['__EVENTARGUMENT'] = ''
    yield FormRequest(
        url='https://inform.alabama.gov/employeesearch.aspx',
        method="POST",
        dont_filter=True,
        formdata=formdata,
        callback=self.perform_search,
        errback=self.failure)

def perform_search(self, response):
    for employee in response.xpath('//*[@id="ContentPlaceHolder1_GridView1"]//tr'):
        yield {
            'name': employee.xpath('./td[1]/text()').get(),
            'email': employee.xpath('./td[1]/span/a/text()').get(),
            'org': employee.xpath('./td[2]/text()').get(),
            'phone': employee.xpath('./td[3]/span/a/text()').get(),
        }

    # Download search pages starting from #2
    for i in range(2, self.num_pages):
        formdata = self.get_formdata(response, i)
        yield FormRequest(
            url='https://inform.alabama.gov/employeesearch.aspx',
            method="POST",
            dont_filter=True,
            formdata=formdata,
            callback=self.parse_results,
            cb_kwargs={
                'page': i,
            },
            errback=self.failure)

def get_formdata(self, response, page_num):
    eventargument = 'Page


Комментарии:

1. Я подключил эти функции к своему классу spider и запустил его с помощью 'scrapy crawl employees -O employees.csv', но на самом деле это не дало никаких результатов: csv заполняется каждой строкой как ',,,'

2. Что-то не так с вашим предположением. У меня 583 элемента на 12 страницах ( dropbox.com/s/xii191yheft2ao4/employees.csv?dl=0 )

3. Я смог получить данные для возврата, указав все пути в yield use '//' : employee.xpath('.//td[1]//text()').get()

4. но затем он пропустил страницы 2 и 3. Могу ли я просмотреть ваш полный файл, чтобы протестировать его непосредственно на моем конце?

5. Что вы подразумеваете под "полным файлом"?

Ответ №2:

С помощью cb_kwargs или старой meta вы можете передавать информацию из одной функции в следующую https://docs.scrapy.org/en/latest/topics/request-response.html

 def start_requests(self):
    for url in self.starting_urls:
        yield Request(
            url,
            cb_kwargs={'additional_arguments': dict_map[url]}
        )

def parse(self, response, additional_arguments):
    # Here you can use that additional_argument
    pass
 

Комментарии:

1. Я не понимаю, как применить это к моему варианту использования. Похоже, что эта функциональность передает статическое содержимое или содержимое запроса в функцию синтаксического анализа. Мне нужно извлекать данные из HTML-ответа страницы после каждого запроса, чтобы сгенерировать данные формы для следующего запроса.

str(page_num)

viewstate = response.css(
'input#__VIEWSTATE::attr(value)').get()
if viewstate is None:
viewstate = ''

viewstategen = response.css(
'input#__VIEWSTATEGENERATOR::attr(value)').get()
if viewstategen is None:
viewstategen = ''

eventvalidation = response.css(
'input#__EVENTVALIDATION::attr(value)').get()
if eventvalidation is None:
eventvalidation = ''

formdata = {
'__EVENTTARGET': 'ctl00$ContentPlaceHolder1$GridView1',
'__EVENTARGUMENT': eventargument,
'__VIEWSTATE': viewstate,
'__VIEWSTATEGENERATOR': viewstategen,
'__EVENTVALIDATION': eventvalidation,
'ctl00$ContentPlaceHolder1$txt_FirstName': '',
'ctl00$ContentPlaceHolder1$txt_LastName': '',
'ctl00$ContentPlaceHolder1$ddl_Agency': 'Not Selected',
'ctl00$ContentPlaceHolder1$txt_Phone': '',
}
return formdata

def parse_results(self, response):
for employee in response.xpath('//*[@id="ContentPlaceHolder1_GridView1"]//tr'):
yield {
'name': employee.xpath('./td[1]/text()').get(),
'email': employee.xpath('./td[1]/span/a/text()').get(),
'org': employee.xpath('./td[2]/text()').get(),
'phone': employee.xpath('./td[3]/span/a/text()').get(),
}

def failure(self, failure):
# log all failures
self.logger.error(repr(failure))

# in case you want to do something special for some errors,
# you may need the failure's type:

if failure.check(HttpError):
# these exceptions come from HttpError spider middleware
# you can get the non-200 response
response = failure.value.response
self.logger.error('HttpError on %s', response.url)

elif failure.check(DNSLookupError):
# this is the original request
request = failure.request
self.logger.error('DNSLookupError on %s', request.url)

elif failure.check(TimeoutError, TCPTimedOutError):
request = failure.request
self.logger.error('TimeoutError on %s', request.url)

Ответ №1:

Вам необходимо сначала отправить запрос формы поиска ( __EVENTTARGET=ctl00$ContentPlaceHolder1$btn_Search ), а затем выполнить итерацию страниц результатов ( __EVENTTARGET=ctl00$ContentPlaceHolder1$GridView1 ).:


Комментарии:

1. Я подключил эти функции к своему классу spider и запустил его с помощью 'scrapy crawl employees -O employees.csv', но на самом деле это не дало никаких результатов: csv заполняется каждой строкой как ',,,'

2. Что-то не так с вашим предположением. У меня 583 элемента на 12 страницах ( dropbox.com/s/xii191yheft2ao4/employees.csv?dl=0 )

3. Я смог получить данные для возврата, указав все пути в yield use '//' : employee.xpath('.//td[1]//text()').get()

4. но затем он пропустил страницы 2 и 3. Могу ли я просмотреть ваш полный файл, чтобы протестировать его непосредственно на моем конце?

5. Что вы подразумеваете под "полным файлом"?

Ответ №2:

С помощью cb_kwargs или старой meta вы можете передавать информацию из одной функции в следующую https://docs.scrapy.org/en/latest/topics/request-response.html


Комментарии:

1. Я не понимаю, как применить это к моему варианту использования. Похоже, что эта функциональность передает статическое содержимое или содержимое запроса в функцию синтаксического анализа. Мне нужно извлекать данные из HTML-ответа страницы после каждого запроса, чтобы сгенерировать данные формы для следующего запроса.

str(page_num)

viewstate = response.css(
'input#__VIEWSTATE::attr(value)').get()
if viewstate is None:
viewstate = ''

viewstategen = response.css(
'input#__VIEWSTATEGENERATOR::attr(value)').get()
if viewstategen is None:
viewstategen = ''

eventvalidation = response.css(
'input#__EVENTVALIDATION::attr(value)').get()
if eventvalidation is None:
eventvalidation = ''

formdata = {
'__EVENTTARGET': 'ctl00$ContentPlaceHolder1$GridView1',
'__EVENTARGUMENT': eventargument,
'__VIEWSTATE': viewstate,
'__VIEWSTATEGENERATOR': viewstategen,
'__EVENTVALIDATION': eventvalidation,
'ctl00$ContentPlaceHolder1$txt_FirstName': '',
'ctl00$ContentPlaceHolder1$txt_LastName': '',
'ctl00$ContentPlaceHolder1$ddl_Agency': 'Not Selected',
'ctl00$ContentPlaceHolder1$txt_Phone': '',
}
return formdata

def parse_results(self, response, page):
# with open(f'Samples/Page_{page}.htm', 'wb') as f:
# f.write(response.body)

for employee in response.xpath('//*[@id="ContentPlaceHolder1_GridView1"]//tr'):
yield {
'name': employee.xpath('./td[1]/text()').get(),
'email': employee.xpath('./td[1]/span/a/text()').get(),
'org': employee.xpath('./td[2]/text()').get(),
'phone': employee.xpath('./td[3]/span/a/text()').get(),
}

Комментарии:

1. Я подключил эти функции к своему классу spider и запустил его с помощью ‘scrapy crawl employees -O employees.csv’, но на самом деле это не дало никаких результатов: csv заполняется каждой строкой как ‘,,,’

2. Что-то не так с вашим предположением. У меня 583 элемента на 12 страницах ( dropbox.com/s/xii191yheft2ao4/employees.csv?dl=0 )

3. Я смог получить данные для возврата, указав все пути в yield use ‘//’ : employee.xpath(‘.//td[1]//text()’).get()

4. но затем он пропустил страницы 2 и 3. Могу ли я просмотреть ваш полный файл, чтобы протестировать его непосредственно на моем конце?

5. Что вы подразумеваете под «полным файлом»?

Ответ №2:

С помощью cb_kwargs или старой meta вы можете передавать информацию из одной функции в следующую https://docs.scrapy.org/en/latest/topics/request-response.html


Комментарии:

1. Я не понимаю, как применить это к моему варианту использования. Похоже, что эта функциональность передает статическое содержимое или содержимое запроса в функцию синтаксического анализа. Мне нужно извлекать данные из HTML-ответа страницы после каждого запроса, чтобы сгенерировать данные формы для следующего запроса.

str(page_num)

viewstate = response.css(
‘input#__VIEWSTATE::attr(value)’).get()
if viewstate is None:
viewstate = »

viewstategen = response.css(
‘input#__VIEWSTATEGENERATOR::attr(value)’).get()
if viewstategen is None:
viewstategen = »

eventvalidation = response.css(
‘input#__EVENTVALIDATION::attr(value)’).get()
if eventvalidation is None:
eventvalidation = »

formdata = {
‘__EVENTTARGET’: ‘ctl00$ContentPlaceHolder1$GridView1’,
‘__EVENTARGUMENT’: eventargument,
‘__VIEWSTATE’: viewstate,
‘__VIEWSTATEGENERATOR’: viewstategen,
‘__EVENTVALIDATION’: eventvalidation,
‘ctl00$ContentPlaceHolder1$txt_FirstName’: »,
‘ctl00$ContentPlaceHolder1$txt_LastName’: »,
‘ctl00$ContentPlaceHolder1$ddl_Agency’: ‘Not Selected’,
‘ctl00$ContentPlaceHolder1$txt_Phone’: »,
}
return formdata

def parse_results(self, response):
for employee in response.xpath(‘//*[@id=»ContentPlaceHolder1_GridView1″]//tr’):
yield {
‘name’: employee.xpath(‘./td[1]/text()’).get(),
’email’: employee.xpath(‘./td[1]/span/a/text()’).get(),
‘org’: employee.xpath(‘./td[2]/text()’).get(),
‘phone’: employee.xpath(‘./td[3]/span/a/text()’).get(),
}

def failure(self, failure):
# log all failures
self.logger.error(repr(failure))

# in case you want to do something special for some errors,
# you may need the failure’s type:

if failure.check(HttpError):
# these exceptions come from HttpError spider middleware
# you can get the non-200 response
response = failure.value.response
self.logger.error(‘HttpError on %s’, response.url)

elif failure.check(DNSLookupError):
# this is the original request
request = failure.request
self.logger.error(‘DNSLookupError on %s’, request.url)

elif failure.check(TimeoutError, TCPTimedOutError):
request = failure.request
self.logger.error(‘TimeoutError on %s’, request.url)

Ответ №1:

Вам необходимо сначала отправить запрос формы поиска ( __EVENTTARGET=ctl00$ContentPlaceHolder1$btn_Search ), а затем выполнить итерацию страниц результатов ( __EVENTTARGET=ctl00$ContentPlaceHolder1$GridView1 ).:


Комментарии:

1. Я подключил эти функции к своему классу spider и запустил его с помощью ‘scrapy crawl employees -O employees.csv’, но на самом деле это не дало никаких результатов: csv заполняется каждой строкой как ‘,,,’

2. Что-то не так с вашим предположением. У меня 583 элемента на 12 страницах ( dropbox.com/s/xii191yheft2ao4/employees.csv?dl=0 )

3. Я смог получить данные для возврата, указав все пути в yield use ‘//’ : employee.xpath(‘.//td[1]//text()’).get()

4. но затем он пропустил страницы 2 и 3. Могу ли я просмотреть ваш полный файл, чтобы протестировать его непосредственно на моем конце?

5. Что вы подразумеваете под «полным файлом»?

Ответ №2:

С помощью cb_kwargs или старой meta вы можете передавать информацию из одной функции в следующую https://docs.scrapy.org/en/latest/topics/request-response.html


Комментарии:

1. Я не понимаю, как применить это к моему варианту использования. Похоже, что эта функциональность передает статическое содержимое или содержимое запроса в функцию синтаксического анализа. Мне нужно извлекать данные из HTML-ответа страницы после каждого запроса, чтобы сгенерировать данные формы для следующего запроса.