Преобразуване на рейк задача, която използва Nokogiri::XML, за да използва Nokogiri:XML::SAX

Имам рейк задача, която работи чудесно на моята локална машина, след разполагане на приложението ми във VPS, тя вече не ми позволява да изпълнявам задачата.

Изпълнявам задачата с помощта на --

RAILS_ENV=production bundle exec rake db:insert_properties

и резултатът, който получавам, е --

(in /home/deployer/apps/nsrosu/releases/20131230151646)
Killed

Някой има ли идея защо може да се случва това? Проверих двойно и тройно дали XML файлът, който използвам за изтегляне на данни за задачата за рейк, съществува в правилната директория.

Освен това се опитах, вместо да използвам файл, съхраняван на сървъра, да го изтегля от външен източник, който се съхранява другаде, но nokogiri казва, че файлът не съществува, когато го опитам по този начин. Решението на всеки един от тези проблеми би било отлично :)

Също така, тук е задачата за рейк, в случай че ще помогне да се отговори на въпроси --

# SET RAKE TASK NAMESPACE
namespace :db do
# RAKE TASK DESCRIPTION
desc "Fetch property information and insert it into the database"

# RAKE TASK NAME    
task :insert_properties => :environment do

    # REQUIRE LIBRARIES
    require 'nokogiri'
    require 'open-uri'

    # OPEN THE XML FILE
    mits_feed = File.open("app/assets/xml/mits.xml")

    # OUTPUT THE XML DOCUMENT
    doc = Nokogiri::XML(mits_feed)

    # FIND PROPERTIES OWNED BY NORTHSTEPPE AND CYCLE THORUGH THEM
    doc.xpath("//Property[PropertyID/Identification/@OrganizationName = 'northsteppe' ]").each do |property|

        # SET UP EMPTY IMAGES ARRAY
        @images =[]

        # INSERT EACH IMAGE INTO THE IMAGES ARRAY
        property.xpath("File").each do |image|
            @images << image.at_xpath("Src/text()").to_s
        end

        # SET UP EXMPTY AMENITIES ARRAY
        @amenities = []

        # INSERT EACH AMENITY DESCRIPTION INTO THE AMENITIES ARRAY
        property.xpath("ILS_Unit/Amenity").each do |image|
            @amenities << image.at_xpath("Description/text()").to_s
        end

        # GATHER EACH PROPERTY'S INFORMATION
        information = {
            "street_address" => property.at_xpath("PropertyID/Address/AddressLine1/text()").to_s,
            "city" => property.at_xpath("PropertyID/Address/City/text()").to_s,
            "zipcode" => property.at_xpath("PropertyID/Address/PostalCode/text()").to_s,
            "short_description" => property.at_xpath("PropertyID/MarketingName/text()").to_s,
            "long_description" => property.at_xpath("Information/LongDescription/text()").to_s,
            "rent" => property.at_xpath("Information/Rents/StandardRent/text()").to_s,
            "application_fee" => property.at_xpath("Fee/ApplicationFee/text()").to_s,
            "bedrooms" => property.at_xpath("Floorplan/Room[@RoomType='Bedroom']/Count/text()").to_s,
            "bathrooms" => property.at_xpath("Floorplan/Room[@RoomType='Bathroom']/Count/text()").to_s,
            "vacancy_status" => property.at_xpath("ILS_Unit/Availability/VacancyClass/text()").to_s,
            "month_available" => property.at_xpath("ILS_Unit/Availability/MadeReadyDate/@Month").to_s,
            "latitude" => property.at_xpath("ILS_Identification/Latitude/text()").to_s,
            "longitude" => property.at_xpath("ILS_Identification/Longitude/text()").to_s,
            "images" => @images,
            "amenities" => @amenities
        }

        # SHOW RAW DATA IN TERMINAL TO MAKE SURE EVERYTHING IS WORKING
        p information


        # CREATE NEW PROPERTY WITH INFORMATION HASH CREATED ABOVE
        if Property.create!(information)
            puts "yay!"
        else
            puts "oh no! this sucks!"
        end

    end # ENDS XPATH EACH LOOP

end # ENDS INSERT_PROPERTIES RAKE TASK

end # ENDS NAMESAPCE DECLARATION

================================= АКТУАЛИЗАЦИЯ =============== ===================

Така че изглежда, че най-добрият подход е да пуснете това през SAX система и SAXMachine е готов да работи с Nokogiri, но документацията и за двете технологии е доста ужасна. Надявах се да получа някаква насока как да настроя задача, която прави същото, което прави моята горна задача, но използвайки SAXMachine. Моля те :)

Публикувах пример за един от XML записите по-долу --

<Property IDValue="642da00e-9be3-4a7c-bd50-66a4f0d70af8">
<PropertyID>
  <Identification IDValue="642da00e-9be3-4a7c-bd50-66a4f0d70af8" OrganizationName="northsteppe" IDType="property"/>
  <Identification IDValue="6e1e61523972d5f0e260e3d38eb488337424f21e" OrganizationName="northsteppe" IDType="Company"/>
  <MarketingName>Spacious House Central Campus OSU, available fall</MarketingName>
  <WebSite>http://northsteppe.appfolio.com/listings/listings/642da00e-9be3-4a7c-bd50-66a4f0d70af8</WebSite>
  <Address AddressType="property">
    <Description>Address of Available Listing</Description>
    <AddressLine1>1689 N 4th St </AddressLine1>
    <City>Columbus</City>
    <State>OH</State>
    <PostalCode>43201</PostalCode>
    <Country>US</Country>
  </Address>
  <Phone PhoneType="office">
    <PhoneNumber>(614) 299-4110</PhoneNumber>
  </Phone>
  <Email>[email protected]</Email>
</PropertyID>
<ILS_Identification ILS_IdentificationType="Apartment" RentalType="Market Rate">
  <Latitude>39.997694</Latitude>
  <Longitude>-82.99903</Longitude>
  <LastUpdate Month="11" Day="11" Year="2013"/>
</ILS_Identification>
<Information>
  <StructureType>Standard</StructureType>
  <UnitCount>1</UnitCount>
  <ShortDescription>Spacious House Central Campus OSU, available fall</ShortDescription>
  <LongDescription>One of our favorites! This great house is perfect for students or a single family. With huge living and sleeping rooms, there is plenty of space. The kitchen is totally modernized with new appliances, and the bathroom has been updated. Natural woodwork and brick accents are seen within the house, and the decorative mantles. Ceiling fans and mini-blinds are included, as well as a FREE stack washer and dryer. The front and side deck. On site parking available.</LongDescription>
  <Rents>
    <StandardRent>2000.00</StandardRent>
  </Rents>
  <PropertyAvailabilityURL>http://northsteppe.appfolio.com/listings/listings/642da00e-9be3-4a7c-bd50-66a4f0d70af8</PropertyAvailabilityURL>
</Information>
<Fee>
  <ProrateType>Standard</ProrateType>
  <LateType>Standard</LateType>
  <LatePercent>0</LatePercent>
  <LateMinFee>0</LateMinFee>
  <LateFeePerDay>0</LateFeePerDay>
  <NonRefundableHoldFee>0</NonRefundableHoldFee>
  <AdminFee>0</AdminFee>
  <ApplicationFee>30.00</ApplicationFee>
  <BrokerFee>0</BrokerFee>
</Fee>
<Deposit DepositType="Security Deposit">
  <Amount AmountType="Actual">
    <ValueRange Exact="2000.00" Currency="USD"/>
  </Amount>
</Deposit>
<Policy>
  <Pet Allowed="false"/>
</Policy>
<Phase IDValue="642da00e-9be3-4a7c-bd50-66a4f0d70af8">
  <Name/>
  <Description/>
  <UnitCount>1</UnitCount>
  <RentableUnits>1</RentableUnits>
  <TotalSquareFeet>0</TotalSquareFeet>
  <RentableSquareFeet>0</RentableSquareFeet>
</Phase>
<Building IDValue="642da00e-9be3-4a7c-bd50-66a4f0d70af8">
  <Name/>
  <Description/>
  <UnitCount>1</UnitCount>
  <SquareFeet>0</SquareFeet>
</Building>
<Floorplan IDValue="642da00e-9be3-4a7c-bd50-66a4f0d70af8">
  <Name/>
  <UnitCount>1</UnitCount>
  <Room RoomType="Bedroom">
    <Count>4</Count>
    <Comment/>
  </Room>
  <Room RoomType="Bathroom">
    <Count>1</Count>
    <Comment/>
  </Room>
  <SquareFeet Min="0" Max="0"/>
  <MarketRent Min="2000" Max="2000"/>
  <EffectiveRent Min="2000" Max="2000"/>
</Floorplan>
<ILS_Unit IDValue="642da00e-9be3-4a7c-bd50-66a4f0d70af8">
  <Units>
    <Unit>
      <Identification IDValue="642da00e-9be3-4a7c-bd50-66a4f0d70af8" OrganizationName="UL Portfolio"/>
      <MarketingName>Spacious House Central Campus OSU, available fall</MarketingName>
      <UnitBedrooms>4</UnitBedrooms>
      <UnitBathrooms>1.0</UnitBathrooms>
      <MinSquareFeet>0</MinSquareFeet>
      <MaxSquareFeet>0</MaxSquareFeet>
      <SquareFootType>internal</SquareFootType>
      <UnitRent>2000.00</UnitRent>
      <MarketRent>2000.00</MarketRent>
      <Address AddressType="property">
        <AddressLine1>1689 N 4th St </AddressLine1>
        <City>Columbus</City>
        <PostalCode>43201</PostalCode>
        <Country>US</Country>
      </Address>
    </Unit>
  </Units>
  <Availability>
    <VacateDate Month="7" Day="23" Year="2014"/>
    <VacancyClass>Unoccupied</VacancyClass>
    <MadeReadyDate Month="7" Day="23" Year="2014"/>
  </Availability>
  <Amenity AmenityType="Other">
    <Description>All new stainless steel appliances!  Refinished hardwood floors</Description>
  </Amenity>
  <Amenity AmenityType="Other">
    <Description>Ceramic tile</Description>
  </Amenity>
  <Amenity AmenityType="Other">
    <Description>Ceiling fans</Description>
  </Amenity>
  <Amenity AmenityType="Other">
    <Description>Wrap-around porch</Description>
  </Amenity>
  <Amenity AmenityType="Dryer">
    <Description>Free Washer and Dryer</Description>
  </Amenity>
  <Amenity AmenityType="Washer">
    <Description>Free Washer and Dryer</Description>
  </Amenity>
  <Amenity AmenityType="Other">
    <Description>off-street parking available</Description>
  </Amenity>
</ILS_Unit>
<File Active="true" FileID="820982141">
  <FileType>Photo</FileType>
  <Description>Unit Photo</Description>
  <Name/>
  <Caption/>
  <Format>image/jpeg</Format>
  <Src>http://pa.cdn.appfolio.com/northsteppe/images/31077069-6e81-4373-8a89-508c57585543/medium.jpg</Src>
  <Width>360</Width>
  <Height>300</Height>
  <Rank>1</Rank>
</File>
<File Active="true" FileID="820982145">
  <FileType>Photo</FileType>
  <Description>Unit Photo</Description>
  <Name/>
  <Caption/>
  <Format>image/jpeg</Format>
  <Src>http://pa.cdn.appfolio.com/northsteppe/images/84e1be40-96fd-4717-b75d-09b39231a762/medium.jpg</Src>
  <Width>350</Width>
  <Height>265</Height>
  <Rank>2</Rank>
</File>
<File Active="true" FileID="820982149">
  <FileType>Photo</FileType>
  <Description>Unit Photo</Description>
  <Name/>
  <Caption/>
  <Format>image/jpeg</Format>
  <Src>http://pa.cdn.appfolio.com/northsteppe/images/cd419635-c37f-4676-a43e-c72671a2a748/medium.jpg</Src>
  <Width>350</Width>
  <Height>265</Height>
  <Rank>3</Rank>
</File>
<File Active="true" FileID="820982152">
  <FileType>Photo</FileType>
  <Description>Unit Photo</Description>
  <Name/>
  <Caption/>
  <Format>image/jpeg</Format>
  <Src>http://pa.cdn.appfolio.com/northsteppe/images/6b68dbd5-2cde-477c-99d7-3ca33f03cce8/medium.jpg</Src>
  <Width>350</Width>
  <Height>265</Height>
  <Rank>4</Rank>
</File>
<File Active="true" FileID="820982155">
  <FileType>Photo</FileType>
  <Description>Unit Photo</Description>
  <Name/>
  <Caption/>
  <Format>image/jpeg</Format>
  <Src>http://pa.cdn.appfolio.com/northsteppe/images/17b6c7c0-686c-4e46-865b-11d80744354a/medium.jpg</Src>
  <Width>350</Width>
  <Height>265</Height>
  <Rank>5</Rank>
</File>
<File Active="true" FileID="820982157">
  <FileType>Photo</FileType>
  <Description>Unit Photo</Description>
  <Name/>
  <Caption/>
  <Format>image/jpeg</Format>
  <Src>http://pa.cdn.appfolio.com/northsteppe/images/3545ac8b-471f-404a-94b2-fcd00dd16e25/medium.jpg</Src>
  <Width>350</Width>
  <Height>265</Height>
  <Rank>6</Rank>
</File>
<File Active="true" FileID="820982160">
  <FileType>Photo</FileType>
  <Description>Unit Photo</Description>
  <Name/>
  <Caption/>
  <Format>image/jpeg</Format>
  <Src>http://pa.cdn.appfolio.com/northsteppe/images/02471172-2183-4bf1-a3d7-33415f902c1c/medium.jpg</Src>
  <Width>350</Width>
  <Height>265</Height>
  <Rank>7</Rank>
</File>


person Noah Davis    schedule 30.12.2013    source източник
comment
KILLED ме кара да мисля, че нещо във вашия VPS убива процеса поради използването на твърде много ресурси може би (RAM/CPU?). Вашият VPS доставчик прави ли такива неща, какъв вид VPS е това?   -  person Doon    schedule 30.12.2013
comment
@Doon - ааа, да. Използвам Rackspace и имам ограничение от 1gb RAM. Задачата за рейк изтегля XML файл от 7 600 000 реда, какво мога да направя, за да поправя това?   -  person Noah Davis    schedule 30.12.2013
comment
Получавате ли по-голям VPS? :). но отговорът на Крис изглежда като добър начин да опитате.   -  person Doon    schedule 30.12.2013
comment
Вашият XML не е добра проба, защото е твърде намален. Трябва поне да видим корена на документа. Но да кажете, че се опитвате да прочетете над 7 милиона реда, също би било достатъчно. Nokogiri по подразбиране е DOM анализатор, така че ще прочете целия документ в RAM, преди да може да започне обработката. SAX процесорът е единствената ви надежда.   -  person the Tin Man    schedule 02.01.2014


Отговори (2)


Ето едно начало за преобразуване, трябва да е достатъчно, за да тръгнете. Освен това не е тестван и мина много време, откакто съм писал SAX код, така че внимавайте.

Първата част е почистване на вашия оригинален код, за да го направите по-скоро като DOM код:

require 'nokogiri'
require 'open-uri'

# doc = Nokogiri::XML(File.open("app/assets/xml/mits.xml"))

# doc.xpath("//Property/PropertyID/Identification/@OrganizationName = 'northsteppe' ]").each do |property|

#   images = property.xpath("File").map { |image|
#     image.at_xpath("Src/text()").to_s 
#   }

#   amenities = property.xpath("ILS_Unit/Amenity").map { |image|
#     image.at_xpath("Description/text()").to_s 
#   }

#   information = {
#     "street_address"    => property.at_xpath("PropertyID/Address/AddressLine1/text()").to_s,
#     "city"              => property.at_xpath("PropertyID/Address/City/text()").to_s,
#     "zipcode"           => property.at_xpath("PropertyID/Address/PostalCode/text()").to_s,
#     "short_description" => property.at_xpath("PropertyID/MarketingName/text()").to_s,
#     "long_description"  => property.at_xpath("Information/LongDescription/text()").to_s,
#     "rent"              => property.at_xpath("Information/Rents/StandardRent/text()").to_s,
#     "application_fee"   => property.at_xpath("Fee/ApplicationFee/text()").to_s,
#     "bedrooms"          => property.at_xpath("Floorplan/Room[@RoomType='Bedroom']/Count/text()").to_s,
#     "bathrooms"         => property.at_xpath("Floorplan/Room[@RoomType='Bathroom']/Count/text()").to_s,
#     "vacancy_status"    => property.at_xpath("ILS_Unit/Availability/VacancyClass/text()").to_s,
#     "month_available"   => property.at_xpath("ILS_Unit/Availability/MadeReadyDate/@Month").to_s,
#     "latitude"          => property.at_xpath("ILS_Identification/Latitude/text()").to_s,
#     "longitude"         => property.at_xpath("ILS_Identification/Longitude/text()").to_s,
#     "images"            => images,
#     "amenities"         => amenities
#   }

#   p information


#   if Property.create!(information)
#     puts "yay!"
#   else
#     puts "oh no! this sucks!"
#   end

# end

Това е началото на SAX кода:

class MitsDocument < Nokogiri::XML::SAX::Document

Дефинирам някои променливи на класа, за да следя images и amenities:

  @@images = []
  @@amenities = []

Всеки път, когато Nokogiri се спусне в таг, той извиква start_element:

  def start_element(tag_name, attributes=[])

    tag_attributes = Hash[*attributes]

    # set up some flags to track the current state...
    @in_property                 = true if (tag_name == 'Property')
    @in_property_id              = true if (tag_name == 'PropertyID')

    @in_identification           = true if (tag_name == 'Identification')
    @organization_is_northsteppe = true if (tag_attributes['OrganizationName'] == 'northsteppe')

    @in_file                     = true if (tag_name == 'File')
    @in_source                   = true if (tag_name == 'Src')

    @in_ils_unit                 = true if (tag_name == 'ILS_Unit')
    @in_amentiy                  = true if (tag_name == 'Amenity')
    @in_description              = true if (tag_name == 'Description')

  end

Когато се срещне текстов възел, characters се извиква. Ако Nokogiri е слязъл достатъчно далеч, което можем да проверим чрез тестване за определени комбинации от флагове, текстът ще бъде избутан в съответния масив:

  def characters(str)
    if [@in_file, @in_source].all?
      @@images << str
    end

    if [@in_ils_unit, @in_amentiy, @in_description].all?
      @@amenities << str
    end
  end

Когато Nokogiri излезе от възел, той извиква end_element с името на етикета:

  def end_element(name)
    @in_property                 = false if (tag_name == 'Property')
    @in_property_id              = false if (tag_name == 'PropertyID')

    @in_identification           = false if (tag_name == 'Identification')
    @organization_is_northsteppe = false if (tag_name == 'Identification')

Ако Nokogiri се прочете, за да излезе от конкретен таг, време е да се направи нещо с обобщените резултати от неговите подтагове. Ето как да се справите с проследяваните променливи на класа:

    if (tag_name == 'File')

      # do something with @@images

      @in_file = false 
    end
    @in_source = false if (tag_name == 'Src')

    if (tag_name == 'ILS_Unit')

      # do something with @@amenities

      @in_ils_unit = false 
    end
    @in_amentiy     = false if (tag_name == 'Amenity')
    @in_description = false if (tag_name == 'Description')

  end

Бихте изчистили DB връзки, или файлове, или където и да е, където съхранявате съдържанието си, когато бъде достигнат краят на документа:

  def end_document
  end
end

parser = Nokogiri::XML::SAX::Parser.new(MitsDocument.new)

# Feed the parser some XML
parser.parse(File.open("app/assets/xml/mits.xml"))

Късно е и съм уморен, така че може да не е правилно, но изглежда като началото. Ще трябва да добавите код, за да обработите проследяването на маркерите във вашия information хеш, но това ще бъде подобно на това, което е по-горе. Също така вероятно бих преминал към използване на case/when изрази вместо списъци с if изрази, за да се опитам да направя набора/изчистването на флагове малко по-чист, но, както казах, уморен съм, така че няма да се занимавам сега .

На „истинско желязо“ срещу работа на виртуална машина, вероятно бихте могли да получите достатъчно RAM, добавена към нея, за да се справите със зареждането на 7M+ ред XML файл. Без целия файл не мога да започна да гадая колко RAM ще заеме това в реалния живот, но това е малко безсмислено. SAX е проектиран да обработва файлове с произволен размер, тъй като SAX обработката наистина разбива цялостния XML на по-малки части, които можете да обработвате по-лесно.

DOM е удобен за повечето неща; През много време виждаме XML да представлява единичен обект или малък извлечение от база данни. Предполагам, че имате работа с голям, до огромен извличане или може би дори пълно изхвърляне на база данни. DOM всъщност не е инструментът за използване в този случай, но SAX е.

Да имаш способността в Nokogiri да се справяш и с двете е хубавото нещо.

person the Tin Man    schedule 02.01.2014
comment
Хей, @theTinMan - Събрах този файл в моята задача за рейк и го пуснах без грешки. Следващият ми въпрос е как да получа достъп до анализираната информация? Например, ако исках да го извикам и да го пусна в базата данни? - person Noah Davis; 06.01.2014

Вероятно надхвърляте лимита на разпределения ви ресурс за вашия VPS и в резултат на това задачата ви се унищожава.

Опциите за подобряване на отпечатъка от паметта на частта за четене на XML на задачата за рейк включват използване на SAX или теглене на парсер вместо зареждане на целия файл в паметта. Вижте „Как мога прочетете голям XML файл в Ruby с libxml-ruby?" за повече подробности.

person Chris Cashwell    schedule 30.12.2013
comment
бихте ли предложили насоки как основно да преобразувате тази рейк задача в използване на саксофон? Или щях да използвам саксофон, за да се откажа от рейк задачата и просто да изтегля данните, когато ми трябват? - person Noah Davis; 31.12.2013